12

I'm using numba to make some functions containing cycles on numpy arrays.

Everything is fine and dandy, I can use jit and I learned how to define the signature.

Now I tried using jit on a function with optional arguments, e.g.:

from numba import jit
import numpy as np

@jit(['float64(float64, float64)', 'float64(float64, optional(float))'])
def fun(a, b=3):
    return a + b

This works, but if instead of optional(float) I use optional(float64) it doesn't (same thing with int or int64). I lost 1 hour trying to figure this syntax out (actually, a friend of mine found this solution by chance because he forgot to write the 64 after the float), but, for the love of me, I cannot understand why this is so. I can't find anything on the internet and numba's docs on the topic are scarce at best (and they specify that optional should take a numba type).

Does anyone know how this works? What am I missing?

MSeifert
  • 145,886
  • 38
  • 333
  • 352
gionni
  • 1,284
  • 1
  • 12
  • 32
  • 1
    Do you need the `'float64(float64, optional(float))'` part at all? I suspect you should just remove it. – user2357112 Sep 08 '17 at 20:08
  • I don't know, I tested and it seemed to run faster that way, but I might be misusing `timeit`. I know it works without the explicit signature, I was just trying to understand how numba works – gionni Sep 09 '17 at 09:21

1 Answers1

11

Ah, but the exception message should give a hint:

from numba import jit
import numpy as np

@jit(['float64(float64, float64)', 'float64(float64, optional(float64))'])
def fun(a, b=3.):
    return a + b

>>> fun(10.)
TypeError: No matching definition for argument type(s) float64, omitted(default=3.0)

That means optional is the wrong choice here. In fact optional represents None or "that type". But you want an optional argument, not an argument that could be a float and None, e.g.:

>>> fun(10, None)  # doesn't fail because of the signature!
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

I suspect that it just "happens" to work for optional(float) because float is just an "arbitary Python object" from numbas point of view, so with optional(float) you could pass anything in there (this apparently includs not giving the argument). With optional(float64) it could only be None or a float64. That category isn't broad enough to allow not providing the argument.

It works if you give the type Omitted:

from numba import jit
import numpy as np

@jit(['float64(float64, float64)', 'float64(float64, Omitted(float64))'])
def fun(a, b=3.):
    return a + b

>>> fun(10.)
13.0

However it seems like Omitted isn't actually included in the documentation and that it has some "rough edges". For example it can't be compiled in nopython mode with that signature, even though it seems possible without signature:

@njit(['float64(float64, float64)', 'float64(float64, Omitted(float64))'])
def fun(a, b=3):
    return a + b

TypingError: Failed at nopython (nopython frontend)
Invalid usage of + with parameters (float64, class(float64))

-----------

@njit(['float64(float64, float64)', 'float64(float64, Omitted(3.))'])
def fun(a, b=3):
    return a + b

>>> fun(10.)
TypeError: No matching definition for argument type(s) float64, omitted(default=3)

-----------

@njit
def fun(a, b=3):
    return a + b

>>> fun(10.)
13.0
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Thankyou! I saw that error, and thought about this `Omitted` thing, but couldn't find anything, or getting it to work anyhow, now I realize I was using `omitted` with lowercase o... Thanks a lot for the examples too :) – gionni Sep 09 '17 at 14:23
  • No problem. But I would suggest that you don't bother with the `Omitted` (and `optional`). These are more internal types if I interpret the documentation correctly. It works best if you *omit* the signature and use `cache=True` (to avoid multiple compilations). – MSeifert Sep 09 '17 at 14:26
  • Won't I get any performance improvement from using the signature? – gionni Sep 09 '17 at 14:28
  • Not really. The signature is more to "limit" the possible inputs and compiling the function up front. Without signature it will infer the types when you call the function and compile it (if necessary) when called (just-in-time - jit). So by providing a signature you can speed up the **first** (and only the first) call to the function for a specific input. – MSeifert Sep 09 '17 at 14:30