I'm defining a function and want to use Numba Vectorize to speed it up, with cuda. I'm having trouble with the function signature. The function will return a float64 value. I want to pass two float64 values, which will be vectorized, and in addition a 9-tuple of float64 values, which will be scalars.
Here is my function header:
from numba import vectorize
#vectorize(['float64(float64, float64, UniTuple(float64, 9))'], target='cuda')
def fn_vec(E, L, fparams):
# calculations...
return result
but this gives an error:
TypeError: data type "(float64 x 9)" not understood
I've tried many variations, including (float64, ..., float64) in place of the UniTuple(), but can't get anything to work. How do I do this?
How do I specify a tuple in a Numba Vectorize signature?
In a numba.vectorize function you cannot use a tuple. That's because vectorize vectorizes the code for arrays of these types.
So using a float, float, tuple signature creates a function that expects two arrays containing floats and one array containing tuples. The problem is that there is no dtype for an array containing tuples - it could work if you use a structured array instead of an array containing tuples but I haven't tried that.
How do I specify a tuple in a Numba jit signature?
The correct way to specify a UniTuple in a numba signature is with numba.types.containers.UniTuple. In your case:
nb.types.containers.UniTuple(nb.types.float64, 9)
So the correct signature would be somthing like this:
import numba as nb
#nb.njit(
nb.types.float64(
nb.types.float64,
nb.types.float64,
nb.types.containers.UniTuple(nb.types.float64, 9)))
def func(f1, f2, ftuple):
# ...
return f1
I often avoid typing my numba functions explicitly - but when I do I found it very useful to use numba.typeof, for example:
>>> nb.typeof((1.0, ) * 9)
tuple(float64 x 9)
>>> type(nb.typeof((1.0, ) * 9))
numba.types.containers.UniTuple
>>> help(type(nb.typeof((1.0, ) * 9))) # I shortened the result:
Help on class UniTuple in module numba.types.containers:
class UniTuple(BaseAnonymousTuple, _HomogeneousTuple, numba.types.abstract.Sequence)
| UniTuple(*args, **kwargs)
|
| Type class for homogeneous tuples.
|
| Methods defined here:
|
| __init__(self, dtype, count)
| Initialize self. See help(type(self)) for accurate signature.
So the information is all there: It's numba.types.containes.UniTuple and you instantiate it with two arguments, the dtype (here float64) and the number (in this case 9).
In case you wanted to vectorize over the float arrays only
If you did not want to vectorize the function for the tuple argument you could simply create the vectorized function inside another function and call it there:
import numba as nb
import numpy as np
def func(E, L, fparams):
#nb.vectorize(['float64(float64, float64)'])
def fn_vec(e, l):
return e + l + fparams[1] # just to illustrate that the tuple is available
return fn_vec(E, L)
This makes the tuple available inside the vectorized function. However it has to create the inner function and compile it everytime you call the outer function, so this may be actually slower. I'm also not sure that this will work with the target="cuda", you may need to test that yourself.
Related
I define a jited function returning a tuple using numba. It's something like below.
import numba as nb
from numba.types import Tuple
import numpy as np
FOO_T = Tuple.from_types((nb.types.NPDatetime('M'), nb.int64))
#nb.jit([FOO_T(nb.types.NPDatetime('M'), nb.types.NPDatetime('M'))], nopython=True)
def foo(input1, input2):
temp1 = input1
temp2 = np.array(input1 - input2).view(nb.int64)
output = (temp1, temp2)
return output
A TypeError is reported as below. The second element of output tuple is defined as int64. However, it's actually compiled as array(int64, 0d, C).
TypingError: No conversion from Tuple(datetime64[M], array(int64, 0d, C)) to Tuple(datetime64[M], int64) for '$38return_value.15', defined at None
Have no idea how to make them consistent. Thanks for your help.
np.array(input1 - input2).view(nb.int64) is an array of int64 and not a scalar. This is why Numba report an error. Note that np.array(input1 - input2) results in a weird type: an array of dimension 0. AFAIK, this is what Numpy use to represent scalars but such an array cannot be indexed in Numba nor converted to a scalar.
You could subtract two scalar and build an array with np.array([input1 - input2]) and then call view. That being said, view is probably not what you want to do here as it reinterpret the binary representation of a NPDatetime as an integer. This is really unsafe and AFAIK there is no reason to assume that this can work. You can just make the difference and cast the result with (np.uint64)(input1 - input2).
I often write functions/methods that take some variable which can come in many forms, i.e., lists of lists, lists of tuples, tuples of tuples, etc. all containing numbers, that I want to convert into a numpy array, kinda like the following:
import numpy as np
def my_func(var: 'what-freaking-type-here') -> np.ndarray:
a = np.asarray(var, dtype=np.float64) # type: np.array[np.float] maybe?
return a
Basically my question is how to type this appropriately, given that I can pass all kinds of values to this function to finally create a 2D float array (note that this just an example and the dimensionality and type should be interchangeable):
my_func([[0], [0]])
my_func([(0,), (2.3,)])
my_func(((0,), [2.3,]))
my_func(np.arange(100).reshape(10, 10))
I have this practice of taking all kinds of values and turning them into numpy arrays in a lot of places, to make working with the functions easy and intuitive. However, I have no idea how to properly type this to verify with mypy. Any hints?
Try the numpy-stubs: experimental typing stubs for NumPy.
It defines the type of the np.array() function like this:
def array(
object: object,
dtype: _DtypeLike = ...,
copy: bool = ...,
subok: bool = ...,
ndmin: int = ...,
) -> ndarray: ...
Which takes any object for the contents and returns an ndarray type.
It's a work in progress. Do report back if it's effective at this stage.
There's also an older project numpy-mypy. As it points out,
Quite a few numpy methods are incredibly flexible and they do their best to accommodate to any possible argument combination. ... Although this is great for users, it caused us a lot of problems when trying to describe the type signature for those methods.
It defines the type of the np.array() function like this:
def array(object: Any, dtype: Any=None, copy: bool=True,
order: str=None, subok: bool=False,
ndmin: int=0) -> ndarray[Any]: ...
Which takes Any for the contents (no type checking there) and returns an ndarray type that's parameterized (generic) by the element type.
I'd like to use Numba to vectorize a function that will evaluate each row of a matrix. This would essentially apply a Numpy ufunc to the matrix, as opposed to looping over the rows. According to the docs:
You might ask yourself, “why would I go through this instead of compiling a simple iteration loop using the #jit decorator?”. The answer is that NumPy ufuncs automatically get other features such as reduction, accumulation or broadcasting.
With that in mind, I can't get even a toy example to work. The following simple example tries to calculate the sum of elements in each row.
import numba, numpy as np
# Define the row-wise function to be vectorized:
#numba.guvectorize(["void(float64[:],float64)"],"(n)->()")
def f(a,b):
b = a.sum()
# Apply the function to an array with five rows:
a = np.arange(10).reshape(5,2)
b = f(a)
I used the #guvectorize decorator, since I'd like the decorated function to take the argument a as each row of the matrix, which is an array; #vectorize takes only scalar inputs. I also wrote the signature to take an array argument and modify a scalar output. As per the docs, the decorated function does not use a return statement.
The result should be b = [1,5,9,13,17], but instead I got b=[0.,1.,2.,3.,4.]. Clearly, I'm missing something. I'd appreciate some direction, keeping in mind that the sum is just an example.
b = a.sum() can't ever modify the original value of b in python syntax.
numba gets around this by requiring every param to a gufunc be an array - scalars are just length 1, that you can then assign into. So you need both params as arrays, and the assignment must use []
#numba.guvectorize(["void(float64[:],float64[:])"],"(n)->()")
def f(a,b):
b[:] = a.sum()
# or b[0] = a.sum()
f(a)
Out[246]: array([ 1., 5., 9., 13., 17.])
#chrisb has a great answer above. This answer should add a bit of clarification for those newer to vectorization.
In terms of vectorization (in numpy and numba), you pass vectors of inputs.
For example:
import numpy as np
a=[1,2]
b=[3,4]
#np.vectorize
def f(x_1,x_2):
return x_1+x_2
print(f(a,b))
#-> [4,6]
In numba, you would traditionally need to pass in input types to the vectorize decorator. In more recent versions of numba, you do not need to specify vector input types if you pass in numpy arrays as inputs to a generically vectorized function.
For example:
import numpy as np
import numba as nb
a=np.array([1,2])
b=np.array([3,4])
# Note a generic vectorize decorator with input types not specified
#nb.vectorize
def f(x_1,x_2):
return x_1+x_2
print(f(a,b))
#-> [4,6]
So far, variables are simple single objects that get passed into the function from the input arrays. This makes it possible for numba to convert the python code to simple ufuncs that can operate on the numpy arrays.
In your example of summing up a vector, you would need to pass data as a single vector of vectors. To do this you need to create ufuncs that operate on vectors themselves. This requires a bit more work and specification for how you want the arbitrary outputs to be created Enter the guvectorize function (docs here and here).
Since you are providing a vector of vectors. Your outer vector is approached similar to how you use vectorize above. Now you need to specify what each inner vector looks like for your input values.
EG adding an arbitrary vector of integers. (This will not work for a few reasons explained below)
#nb.guvectorize([(nb.int64[:])])
def f(x):
return x.sum()
Now you will also need to add an extra input to your function and decorator. This allows you to specify an arbitrary type to store the output of your function. Instead of returning output, you will now update this input variable. Think of this final variable as a custom variable numba uses to generate an arbitrary output vector when creating the ufunc for numpy evaluation.
This input also needs to be specified in the decorator and your function should look something like this:
#nb.guvectorize([(nb.int64[:],nb.int64[:])])
def f(x, out):
out[:]=x.sum()
Finally you need to specify input and output formats in the decorator. These are given as matrix shapes in the order of input vectors and uses an arrow to indicate the output vector shape (which is actually your final input). In this case you are taking a vector of size n and outputing the results as a value and not a vector. Your format should be (n)->().
As a more complex example, assuming you have two input vectors for matrix multiplication of size (m,n) and (n,o) and you wanted your output vector to be of size (m,o) your decorator format would look like (m,n),(n,o)->(m,o).
A complete function for the current problem would look something like:
#nb.guvectorize([(nb.int64[:],nb.int64[:])], '(n)->()')
def f(x, out):
out[:]=x.sum()
Your end code should look something like:
import numpy as np
import numba as nb
a=np.arange(10).reshape(5,2)
# Equivalent to
# a=np.array([
# [0,1],
# [2,3],
# [4,5],
# [6,7],
# [8,9]
# ])
#nb.guvectorize([(nb.int64[:],nb.int64[:])], '(n)->()')
def f(x, out):
out[:]=x.sum()
print(f(a))
#-> [ 1 5 9 13 17]
I've been experimenting with Numba lately, and here's something that I still cannot understand:
In a normal Python function with NumPy arrays you can do something like this:
# Subtracts two NumPy arrays and returns an array as the result
def sub(a, b):
res = a - b
return res
But, when you use Numba's #guvectorize decorator like so:
# Subtracts two NumPy arrays and returns an array as the result
#guvectorize(['void(float32[:], float32[:], float32[:])'],'(n),(n)->(n)')
def subT(a, b, res):
res = a - b
The result is not even correct. Worse still, there are instances where it complains about "Invalid usage of [math operator] with [parameters]"
I am baffled. Even if I try this:
# Subtracts two NumPy arrays and returns an array as the result
#guvectorize(['void(float32[:], float32[:], float32[:])'],'(n),(n)->(n)')
def subTt(a, b, res):
res = np.subtract(a,b)
The result is still incorrect. Considering that this is supposed to be a supported Math operation, I don't see why it doesn't work.
I know the standard way is like this:
# Subtracts two NumPy arrays and returns an array as the result
#guvectorize(['void(float32[:], float32[:], float32[:])'],'(n),(n)->(n)')
def subTtt(a, b, res):
for i in range(a.shape[0]):
res[i] = a[i] - b[i]
and this does work as per expected.
But what is wrong with my way?
P/S This is just a trivial example to explain my problem, I don't actually plan to use #guvectorize just to subtract arrays :P
P/P/S I suspect it has something to do with how the arrays are copied to gpu memory, but I am not sure...
P/P/P/S This looked relevant but the function here operates only on a single thread right...
The correct way to write this is:
#guvectorize(['void(float32[:], float32[:], float32[:])'],'(n),(n)->(n)')
def subT(a, b, res):
res[:] = a - b
The reason what you tried didn't work is a limitation of python syntax not particular to numba.
name = expr rebinds the value of name to expr, it can never mutate the original value of name, as you could with, e.g. c++ references.
name[] = expr calls (in essence), name.__setitem__ which can be used to modify name, as numpy arrays do, the empty slice [:] refers to the whole array.
I wonder if anyone has an elegant solution to being able to pass a python list, a numpy vector (shape(n,)) or a numpy vector (shape(n,1)) to a function. The idea would be to generalize a function such that any of the three would be valid without adding complexity.
Initial thoughts:
1) Use a type checking decorator function and cast to a standard representation.
2) Add type checking logic inline (significantly less ideal than #1).
3) ?
I do not generally use python builtin array types, but suspect a solution to this question would also support those.
I think the simplest thing to do is to start off your function with numpy.atleast_2d. Then, all 3 of your possibilities will be converted to the x.shape == (n, 1) case, and you can use that to simplify your function.
For example,
def sum(x):
x = np.atleast_2d(x)
return np.dot(x, np.ones((x.shape[0], 1)))
atleast_2d returns a view on that array, so there won't be much overhead if you pass in something that's already an ndarray. However, if you plan to modify x and therefore want to make a copy instead, you can do x = np.atleast_2d(np.array(x)).
You can convert the three types to a "canonical" type, which is a 1dim array, using:
arr = np.asarray(arr).ravel()
Put in a decorator:
import numpy as np
import functools
def takes_1dim_array(func):
#functools.wraps(func)
def f(arr, *a, **kw):
arr = np.asarray(arr).ravel()
return func(arr, *a, **kw)
return f
Then:
#takes_1dim_arr
def func(arr):
print arr.shape