Hey @MLLO,
Numba offers two compilation modes: “lazy” and “eager.” In lazy mode, you can use the jit
or njit
decorators, and Numba takes care of most of the compilation for you. This approach is much easier. It has its advantages and disadvantages.
On the other hand, in the “eager” compilation mode, you must ensure that your function’s signature is absolutely correct because Numba tries to compile the function during the first call and will raise errors if there are any issues. You have to be pedantic.
Unfortunately, creating the right signature for more complex types is not always well-documented, and you might have to dive into the source code for guidance. Two helpful tools are the numba.typeof
function and the signatures
attribute of the compiled function.
Most of the time, I prefer to start with lazy compilation to let Numba infer the correct signatures and then reverse engineer the signature based on what I observe. This way, you can fine-tune the function’s type annotations until it works as expected in eager compilation mode. It might take some trial and error which can be frustrating sometimes.
Identifying your problems is quite difficult without a complete code context. Potential problems might arise from input data types not aligning with your Numba function signature, implicit type conversions, or an incorrect function signature.
Here is an example which should help you figure it out. I replaced your triangle class with a namedtuple for simplicity.
Happy weekend!
from collections import namedtuple
import numpy as np
import numba as nb
import numba.types as nbt
# I replaced your triangle class with a namedtuple for simplicity
Point = namedtuple('Point', ('x', 'y'))
Triangle = namedtuple('Triangle', ('p0', 'p1', 'p2'))
PointType = nbt.NamedUniTuple(nbt.f4, 2, Point)
TriangleType = nbt.NamedUniTuple(PointType, 3, Triangle)
# Check your array layout <Array>.flags
# It is probably C_CONTIGUOUS
Arr2DFloat32C = nb.f4[:, ::1]
ResultList = nb.types.ListType(Arr2DFloat32C)
# Compare your generated signature with the signature of lazy compiliation
Signature = ResultList(TriangleType, Arr2DFloat32C, Arr2DFloat32C)
@nb.njit(Signature)
def calculate_barycentric_weights_for_all_pts(t, X, Y):
p0, p1, p2 = t
# Generate an empty typed list
res = nb.typed.List.empty_list(Arr2DFloat32C)
area = 0.5 * (p0.x * (p1.y - p2.y) + p1.x * (p2.y - p0.y) + p2.x * (p0.y - p1.y))
# prevent implicit conversion to float64 using astype(np.float32)
# append the results to your list
res.append((0.5 * (X * (p1.y - p2.y) + p1.x * (p2.y - Y) + p2.x * (Y - p1.y)) / area).astype(np.float32))
res.append((0.5 * (p0.x * (Y - p2.y) + X * (p2.y - p0.y) + p2.x * (p0.y - Y)) / area).astype(np.float32))
res.append((0.5 * (p0.x * (p1.y - Y) + p1.x * (Y - p0.y) + X * (p0.y - p1.y)) / area).astype(np.float32))
return res
X = np.ones((2, 2), dtype=np.float32)
Y = np.ones((2, 2), dtype=np.float32)
ZERO = np.float32(0)
ONE = np.float32(1)
p0 = Point(ZERO, ZERO)
p1 = Point(ONE, ZERO)
p2 = Point(ZERO, ONE)
triangle = Triangle(p0, p1, p2)
print(calculate_barycentric_weights_for_all_pts(triangle, X, Y))
print(calculate_barycentric_weights_for_all_pts.signatures)
# [[[-1. -1.] [-1. -1.]], [[1. 1.] [1. 1.]], [[1. 1.] [1. 1.]], ...]
# [(Triangle(Point(float32 x 2) x 3), Array(float32, 2, 'C', False, aligned=True), Array(float32, 2, 'C', False, aligned=True))]