How to update a jitclass variable with its string name by passing a setter function to the jitclass itself?

Viewed 120

I'm seeking a way to modify a jitclass variable with its name as a string. I tried to write a setter and getter function (get_A and set_A), and I got the method outside the jitclass. I would like then to pass that method to a jitclass method to update the value of the variable. However I get and error saying:

numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
non-precise type pyobject
[1] During: typing of argument at <string> (3)

File "<string>", line 3:
<source missing, REPL/exec in use?>

This error may have been caused by the following argument(s):
- argument 1: cannot determine Numba type of <class 'tuple'>

Here the minimal example:

from numba import float64
from numba.experimental import jitclass

spec = [('A', float64), ('B', float64), ('C', float64)]

@jitclass(spec)
class maClass:
    def __init__(self):
        self.A = 0

    def get_A(self):
        return self.A

    def set_A(self, x):
        self.A = x

    def update(self, func, val):
        func(val)

C1 = maClass()
print('A', C1.A)

Names= ['A']
vals= [1.]

func = getattr(C1, 'set_' + Names[0])
print(func)

C1.update(func, vals[0])
print('A', C1.A)
2 Answers

Here is a solution I came up with. I first wrote a function that return a list of the variable names I can update

def get_Variable_Names():
    return ['A','B','C']

then, in the jitclass, I wrote a function that can update variable according to an index based on the order of get_Variable_Names.

def update_val(self, i, var):
    if i == 0:
        self.A = var
    elif i == 1:
        self.B = var
    elif i == 2:
        self.C = var

To use this function, I search for the index of a variable name in the list returned by get_Variable_Names and then call the update_val function of the jitclass.

listnames = get_Variable_Names()
val = 1.
name = 'A'
index = listnames.index(name)
C1.update_val(index,val)

here's all the code.

from numba import jitclass, int32, float64

def get_Variable_Names():
    return ['A','B','C']

spec = [('A' ,float64),('B' ,float64),('C' ,float64)]

@jitclass(spec)
class maClass():
    def __init__(self,):
        self.A = 0.
        self.B = 0.
        self.C = 0.

    def update_val(self, i, var):
        if i == 0:
            self.A = var
        elif i == 1:
            self.B = var
        elif i == 2:
            self.C = var

C1 = maClass()
listnames = get_Variable_Names()
val = 1.
name = 'A'
index = listnames.index(name)
C1.update_val(index,val)

val = 2.
name = 'B'
index = listnames.index(name)
C1.update_val(index,val)

val = 3.
name = 'C'
index = listnames.index(name)
C1.update_val(index,val)
print('A',C1.A)
print('B',C1.B)
print('C',C1.C)

It is not was I was looking for, but it is find for now.

I'm still waiting for something better.

I even wrote a script to write the if else update_val function, because It can be tedious when we have a large amount of variables to modify

listnames= ['A','B','C']

for i, n in enumerate(listnames):
    if i == 0:
        print('if i == ' + str(i)+ ' :\n\tself.' + n + ' = var')
    else:
        print('elif i == ' + str(i) + ' :\n\tself.' + n + ' = var')

With a couple of small changes, you can in fact use function pointers in numba. I came up with the following based on a whole bunch of experimentation, and the first item on the FAQ.

The first thing to notice is that getattr(C1, 'set_A') returns a Python method object. This is not something that is directly supported in numba at time of writing (v0.53.1). Instead, you have to get a pointer to the actual jitted function, which you can do through the relatively undocumented _numba_type_ attribute. This contains a bunch of descriptors of the methods and attributes of your object that are quite useful. Specifically, your func object is given by

func = C1._numba_type_.jit_methods['set_A']

The second thing to note is that the compiled function func requires an explicit self argument that is a subtype of maClass.class_type.instance_type. Calling it with only one argument will cause a compilation error in update:

func(self, val)

Here is the complete example (including the fixed jitclass import):

from numba import float64
from numba.experimental import jitclass

spec = [('A', float64), ('B', float64), ('C', float64)]

@jitclass(spec)
class maClass:
    def __init__(self):
        self.A = self.B = self.C = 0

    def get_A(self):
        return self.A

    def set_A(self, x):
        self.A = x

    def update(self, func, val):
        func(self, val)

C1 = maClass()
print('A', C1.A)

Names = ['A']
vals = [1.]

func = C1._numba_type_.jit_methods['set_' + Names[0]]
print(func)

C1.update(func, float64(vals[0]))
print('A', C1.A)
Related