Using lists in Numba @jitclass

Viewed 2724

I am simulating a very lame game that basically counts the amount of coins and enemies a player collects as she progresses in the game. The code contains two jitclasses: a player jitclass and a game jitclass.

For the player class we have some attributes and some methods that describe the progression of the player along the game.

from numba import jitclass, int64, float64, deferred_type
from numba.typed import List
import random


specs_player = OrderedDict()
specs_player['level'] = int64
specs_player['coins'] = float64


@jitclass(specs_player)
class Player:

    def __init__(self):
        self.level = 0
        self.coins = 0
        self.enemies = List()

    def pass_level(self):
        self.level += 1

    def collect_coins(self, c):
        self.coins += c

    def collect_enemies(self, e):
        self.enemies.append(e)

    def reset_player(self):
        self.level = 0
        self.coins = 0
        self.enemies = List()

As you can see, the attribute enemies is a list that gets the values appended as the player progresses in the game.

The game jitclass calls the player jitclass as an attribute using the first two lines:

Player_type = deferred_type()
Player_type.define(Player.class_type.instance_type)

specs_Game = OrderedDict()
specs_Game['last_level'] = int64
specs_Game['diff_threshold'] = float64
specs_Game['player'] = Player_type



class Game:

    def __init__(self, l, t):
        self.player = Player()
        self.last_level = l
        self.diff_threshold = t

    def play_gameround(self):

        random_draw = random.uniform(0, 1)

        if random_draw > self.diff_threshold:
            # Pass Level
            self.player.pass_level()

            # Collect coins
            coins_earned = 100*(random_draw - self.diff_threshold)
            self.player.collect_coins(coins_earned)

            #Collect enemies
            if coins_earned > 10:
                self.player.collect_enemies()


    def reset_gameplay(self):
        self.player.reset_player()

    def continue_playing(self):
        condition = self.player.level < self.last_level

        return(condition)


Finally, a function called run_one_player simulates the progression of one player and returns an array with all the necessary data:

def run_one_player(gameplay):
    while gameplay.continue_playing():
        gameplay.play_gameround()

        player_data = np.array([gameplay.player.level,
                                gameplay.player.coins,
                                gameplay.player.enemies])

    return (player_data)

To run the code I just type:

g = Game(l = 10, t = 0.5)

data_list = run_one_player(g)

This, however, does not work and Numba returns the following error, that I am pretty sure it comes from the fact that I am not correctly defining the Numba type for the enemies field.

---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
<ipython-input-51-f6340d9cfcd7> in <module>
      1 players = 10
----> 2 g = Game(l = 10, t = 0.5)
      3 
      4 data_list = run_one_player(g)

<ipython-input-49-6e42f0104cf1> in __init__(self, l, t)
     46 
     47     def __init__(self, l, t):
---> 48         self.player = Player()
     49         self.last_level = l
     50         self.diff_threshold = t

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/jitclass/base.py in __call__(cls, *args, **kwargs)
    124         bind = cls._ctor_sig.bind(None, *args, **kwargs)
    125         bind.apply_defaults()
--> 126         return cls._ctor(*bind.args[1:], **bind.kwargs)
    127 
    128 

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    374                 e.patch_message(msg)
    375 
--> 376             error_rewrite(e, 'typing')
    377         except errors.UnsupportedError as e:
    378             # Something unsupported is present in the user code, add help info

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/dispatcher.py in error_rewrite(e, issue_type)
    341                 raise e
    342             else:
--> 343                 reraise(type(e), e, None)
    344 
    345         argtypes = []

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numba/six.py in reraise(tp, value, tb)
    656             value = tp()
    657         if value.__traceback__ is not tb:
--> 658             raise value.with_traceback(tb)
    659         raise value
    660 

TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Failed in nopython mode pipeline (step: nopython frontend)
Cannot resolve setattr: (instance.jitclass.Player#123b442d0<level:int64,coins:float64>).enemies = ListType[undefined]

File "<ipython-input-49-6e42f0104cf1>", line 18:
    def __init__(self):
        <source elided>
        self.coins = 0
        self.enemies = List()
        ^

[1] During: typing of set attribute 'enemies' at <ipython-input-49-6e42f0104cf1> (18)

File "<ipython-input-49-6e42f0104cf1>", line 18:
    def __init__(self):
        <source elided>
        self.coins = 0
        self.enemies = List()
        ^

[1] During: resolving callee type: jitclass.Player#123b442d0<level:int64,coins:float64>
[2] During: typing of call at <string> (3)

[3] During: resolving callee type: jitclass.Player#123b442d0<level:int64,coins:float64>
[4] During: typing of call at <string> (3)


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

This is not usually a problem with Numba itself but instead often caused by
the use of unsupported features or an issue in resolving types.

To see Python/NumPy features supported by the latest release of Numba visit:
http://numba.pydata.org/numba-doc/latest/reference/pysupported.html
and
http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html

For more information about typing errors and how to debug them visit:
http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html#my-code-doesn-t-compile

If you think your code should work with Numba, please report the error message
and traceback, along with a minimal reproducer at:
https://github.com/numba/numba/issues/new


1 Answers

First: I don't think you should use numba for something like this. Numba is a specialized tool that is very good at solving a particular kind of problems and this isn't one of them:

1.1.2. Will Numba work for my code?

This depends on what your code looks like, if your code is numerically orientated (does a lot of math), uses NumPy a lot and/or has a lot of loops, then Numba is often a good choice

[...]


However in your particular situation you need to fully type all attributes of your jitclass. That means you have to type enemies with a type numba understands (either one of the supported types or another jitclass) otherwise it will not work.

Since you haven't provided what the type is let's just assume it would be an integer:

import numba as nb

specs_player = {}
specs_player['level'] = nb.int64
specs_player['coins'] = nb.float64
specs_player['enemies'] = nb.types.List(nb.int64)

@nb.jitclass(specs_player)
class Player:
    def __init__(self):
        self.level = 0
        self.coins = 0
        self.enemies = []

This fails when you create a new instance because numba cannot infer the type of an empty list (at least at the moment). So you would have to initialize with some type. I haven't found a better way than creating a list with an item and then clearing it:

import numba as nb

specs_player = {}
specs_player['level'] = nb.int64
specs_player['coins'] = nb.float64
specs_player['enemies'] = nb.types.List(nb.int64)

@nb.njit
def empty_int64_list():
    l = [nb.int64(10)]
    l.clear()
    return l

@nb.jitclass(specs_player)
class Player:
    def __init__(self):
        self.level = 0
        self.coins = 0
        self.enemies = empty_int64_list()

It will probably be a lot more complicated if your enemies aren't integers. However I don't think that it's worth it in your case because this isn't a problem that numba will solve (significantly) more efficiently than pure Python.

Related