numba / numba

NumPy aware dynamic Python compiler using LLVM
https://numba.pydata.org/
BSD 2-Clause "Simplified" License
9.95k stars 1.13k forks source link

Variable '$88setup_with.1' is not defined. #6142

Closed OfekShochat closed 4 years ago

OfekShochat commented 4 years ago

I'm making a chess engine in python (I would do it in c but this is for fun) and it is pretty good but really slow. so I wanted to make it faster with numba njit. but it gets an error. full error code:

Traceback (most recent call last):
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\ir.py", line 267, in get
    return self._con[name]
KeyError: '$88setup_with.1'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\ir.py", line 1119, in get_exact
    return self.localvars.get(name)
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\ir.py", line 269, in get
    raise NotDefinedError(name)
numba.core.errors.NotDefinedError: Variable '$88setup_with.1' is not defined.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\ir.py", line 267, in get
    return self._con[name]
KeyError: '$88setup_with.1'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "E:\lichess-bot\engines\engine2\main.pyx", line 320, in <module>
    main2()
  File "E:\lichess-bot\engines\engine2\main.pyx", line 317, in main2
    return negaa(board, depth, -inf, 10000, color)
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\dispatcher.py", line 423, in _compile_for_args
    error_rewrite(e, 'interpreter')
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\dispatcher.py", line 358, in error_rewrite
    reraise(type(e), e, None)
  File "C:\Users\o\AppData\Local\Programs\Python\Python38-32\lib\site-packages\numba\core\utils.py", line 80, in reraise
    raise value.with_traceback(tb)
numba.core.errors.NotDefinedError: Failed in nopython mode pipeline (step: analyzing bytecode)
Variable '$88setup_with.1' is not defined.

and the jited function:

    st = time()
    try:
        move = opening_move(board, book_path)
        print("""{"move":"%s"}""" % move)
        return move
    except IndexError:
        pass
    value = -inf
    possible = tuple(board.legal_moves)
    with tqdm(total=len(possible), unit="move") as pbar:
        for move in possible:
            #tomove = True
            Thread(target=update_nodes, args=[pbar, st]).start()
            #assert board.is_legal(move)
            board.push(move)

            firstguess = -negamax(board, depth, -10000, 10000, color)
            #tomove = False
            board.pop()
            if str(move) == "c8c1":
               print( " " + str(move), firstguess, "fff")
            if firstguess > value:
               best_move = move
               value = firstguess
               #print( " " + str(best_move), value)
            if firstguess >= 10000:
               break
            pbar.update(1)
    print("""{
    "depth": %s,
    "score": %s,
    "move": "%s",
    "time_took": %s,
    "nodes searched": %s,
    "nodes per second": %s}""" % (depth, value, best_move, time() - st, nodesss, nodesss/(time() - st)))
    return best_move
esc commented 4 years ago

@OfekShochat thanks for submitting this! It seems like you may have accidentally missed some lines when copy and pasting the example above. In order to reproduce it, I will need a runnable example (ideally including all imports). Thanks!

OfekShochat commented 4 years ago

thanks.this is the full code(sorry if this is long...):

from math import inf
import chess
from time import time
import sys
import os
import chess.polyglot as bookloader
import json
from tqdm import tqdm
import pyximport; pyximport.install()
from numba import njit
from multiprocessing import Process
from threading import Thread
from time import sleep

################################### piece squre table ###################################
# piece square tables (for analysis) (change these to change the engine behavior)
#import numpy as np
bPawnTable = (   0,   0,   0,   0,   0,   0,   0,   0,
            78,  83,  86,  73, 102,  82,  85,  90,
             7,  29,  21,  44,  40,  31,  44,   7,
           -17,  16,  -2,  15,  14,   0,  15, -13,
           -26,   3,  10,   9,   6,   1,   0, -23,
           -22,   9,   5, -11, -10,  -2,   3, -19,
           -31,   8,  -7, -37, -36, -14,   3, -31,
             0,   0,   0,   0,   0,   0,   0,   0)
wPawnTable = (0,0,0,0,0,0,0,0,
              10,8,9,5,3,6,2,5,
              11, 6,5,6,8,3,10,
              -6,5,3,60,60,4,6,1,
              10, 11, 12, 11, 10, 9, 5,3,
              12, 13, 14, 15,14, 13, 12, 12,
              70, 70, 80, 70, 70, 80 , 80, 80,
              90,90,90,90,90,90,90,90)

bKnightTable = ( -66, -53, -75, -75, -10, -55, -58, -70,
            -3,  -6, 100, -36,   4,  62,  -4, -14,
            10,  67,   1,  74,  73,  27,  62,  -2,
            24,  24,  45,  37,  33,  41,  25,  17,
            -1,   5,  31,  21,  22,  35,   2,   0,
           -18,  10,  13,  22,  18,  15,  11, -14,
           -23, -15,   2,   0,   2,   0, -23, -20,
           -74, -23, -26, -24, -19, -35, -22, -69)
wKnightTable = bKnightTable[::-1]
bBishopTable = ( -59, -78, -82, -76, -23,-107, -37, -50,
           -11,  20,  35, -42, -39,  31,   2, -22,
            -9,  39, -32,  41,  52, -10,  28, -14,
            25,  17,  20,  34,  26,  25,  15,  10,
            13,  10,  17,  23,  17,  16,   0,   7,
            14,  25,  24,  15,   8,  25,  20,  15,
            19,  20,  11,   6,   7,   6,  20,  16,
            -7,   2, -15, -12, -14, -15, -10, -10)
wBishopTable = bBishopTable[::-1]
bRookTable = (  35,  29,  33,   4,  37,  33,  56,  50,
            55,  29,  56,  67,  55,  62,  34,  60,
            19,  35,  28,  33,  45,  27,  25,  15,
             0,   5,  16,  13,  18,  -4,  -9,  -6,
           -28, -35, -16, -21, -13, -29, -46, -30,
           -42, -28, -42, -25, -25, -35, -26, -46,
           -53, -38, -31, -26, -29, -43, -44, -53,
           -30, -24, -18,   5,  -2, -18, -31, -32)
wRookTable = bRookTable[::-1]
bQueenTable =  (   6,   1,  -8,-104,  69,  24,  88,  26,
            14,  32,  60, -10,  20,  76,  57,  24,
            -2,  43,  32,  60,  72,  63,  43,   2,
             1, -16,  22,  17,  25,  20, -13,  -6,
           -14, -15,  -2,  -5,  -1, -10, -20, -22,
           -30,  -6, -13, -11, -16, -11, -16, -27,
           -36, -18,   0, -19, -15, -15, -21, -38,
           -39, -30, -31, -13, -31, -36, -34, -42)
wQueenTable = bQueenTable[::-1]
bKingTable = (   4,  54,  47, -99, -99,  60,  83, -62,
           -32,  10,  55,  56,  56,  55,  10,   3,
           -62,  12, -57,  44, -67,  28,  37, -31,
           -55,  50,  11,  -4, -19,  13,   0, -49,
           -55, -43, -52, -28, -51, -47,  -8, -50,
           -47, -42, -43, -79, -64, -32, -29, -32,
            -4,   3, -14, -50, -57, -18,  13,   4,
            17,  30,  -3, -14,   6,  -1,  40,  18)
wKingTable = bKingTable[::-1]

king_safty = (
  0,  0,   1,   2,   3,   5,   7,   9,  12,  15,
  18,  22,  26,  30,  35,  39,  44,  50,  56,  62,
  68,  75,  82,  85,  89,  97, 105, 113, 122, 131,
 140, 150, 169, 180, 191, 202, 213, 225, 237, 248,
 260, 272, 283, 295, 307, 319, 330, 342, 354, 366,
 377, 389, 401, 412, 424, 436, 448, 459, 471, 483,
 494, 500, 500, 500, 500, 500, 500, 500, 500, 500,
 500, 500, 500, 500, 500, 500, 500, 500, 500, 500,
 500, 500, 500, 500, 500, 500, 500, 500, 500, 500,
 500, 500, 500, 500, 500, 500, 500, 500, 500, 500
)

tables = {"doubled":-1, "isolated": -3,"-100": bPawnTable, "100":wPawnTable, "-320":bKnightTable, "320":wKnightTable, "-330":bBishopTable,"330":wBishopTable,"500":wRookTable,"-500":bRookTable,"900":wQueenTable, "-900":bQueenTable,"-20000":bKingTable, "20000":wKingTable}
weights = {"mobility": 0.2, "piece_table":0.1, "king_table":0.4} # np.random.random_sample()

################################### /* piece squre table /* ###################################

def opening_move(board, book_path):
   return bookloader.open_reader(book_path).find(board).move

################################### evaluation ###################################

def get_piece_val(piece:str):
   temp = 0
   if piece == "k":
      temp = -200
   if piece == "q":
      temp = -9
   if piece == "r":
      temp = -5
   if piece == "b":
      temp = -3.4
   if piece == "n":
      temp = -3.2
   if piece == "p":
      temp = -1
   if piece == "K":
      temp = 200
   if piece == "Q":
      temp = 9
   if piece == "R":
      temp = 5
   if piece == "B":
      temp = 3.3
   if piece == "N":
      temp = 3.20
   if piece == "P":
      temp = 1
   return temp

def passed_pawn(pm, is_end_game):
    whiteYmax = [ -1 ] * 8
    blackYmin = [ 8 ] * 8

    for key, p in pm.items():
        if p.piece_type != chess.PAWN:
            continue

        x = key & 7
        y = key >> 3

        if p.color == chess.WHITE:
            whiteYmax[x] = max(whiteYmax[x], y)
        else:
            blackYmin[x] = min(blackYmin[x], y)

    scores = [ [ 0, 5, 20, 30, 40, 50, 80, 0 ], [ 0, 5, 20, 40, 70, 120, 200, 0 ] ]

    score = 0

    for key, p in pm.items():
        if p.piece_type != chess.PAWN:
            continue

        x = key & 7
        y = key >> 3

        if p.color == chess.WHITE:
            left = (x > 0 and (blackYmin[x - 1] <= y or blackYmin[x - 1] == 8)) or x == 0
            front = blackYmin[x] < y or blackYmin[x] == 8
            right = (x < 7 and (blackYmin[x + 1] <= y or blackYmin[x + 1] == 8)) or x == 7

            if left and front and right:
                score += scores[is_end_game][y]

        else:
            left = (x > 0 and (whiteYmax[x - 1] >= y or whiteYmax[x - 1] == -1)) or x == 0
            front = whiteYmax[x] > y or whiteYmax[x] == -1
            right = (x < 7 and (whiteYmax[x + 1] >= y or whiteYmax[x + 1] == -1)) or x == 7

            if left and front and right:
                score -= scores[is_end_game][7 - y] 
    return score

def evaluate(board_fen, debugger=False):
   thisboard = chess.Board(board_fen)
   if list(chess.LegalMoveGenerator(thisboard)) == []:
      if thisboard.is_check():
         return 10000
      return 0
   if thisboard.is_insufficient_material():
      return 0
   evaluation = 0
   index = 0

   for i in str(thisboard):
      evaluation += get_piece_val(i)
      try:
         piece_val = get_piece_val(i)
         if piece_val != 20000:
            evaluation += tables[str(piece_val)][index] * weights["piece_table"]
         else:
            evaluation += tables[str(piece_val)][index] * weights["king_table"]
      except KeyError:
         index -= 1
      index += 1
   for i in chess.LegalMoveGenerator(thisboard): evaluation += 1 * weights["mobility"]
   if list(chess.LegalMoveGenerator(thisboard)) == []:
      if thisboard.is_check():
         return 10000
      return 0
   evaluation += passed_pawn(thisboard.piece_map(), False)
   move = list(thisboard.legal_moves)[0]
   move = chess.Move.from_uci(str(move))
   thisboard.push(move)
   for i in chess.LegalMoveGenerator(thisboard): evaluation -= 1 * weights["mobility"]
   return evaluation

def update_nodes(pbar, st):
   while tomove:
      sleep(1)
      pbar.set_description("calculating %s at %s nodes/s" % (str(move), nodesss/(time() - st)))

def negamax_root(board, depth, alpha, beta, color):
    global tomove
    global move
    tomove = True
    st = time()
    value = -inf
    possible = tuple(board.legal_moves)
    with tqdm(total=len(possible), unit="move") as pbar:
        nodesssss = Thread(target=update_nodes, args=[pbar, st])
        nodesssss.daemon = True
        nodesssss.start()
        for move in possible:
            tomove = True

            #assert board.is_legal(move)
            board.push(move)

            firstguess = -negamax(board, depth, -10000, 10000, color)
            board.pop()
            if str(move) == "c8c1":
               print( " " + str(move), firstguess, "fff")
            if firstguess > value:
               best_move = move
               value = firstguess
               #print( " " + str(best_move), value)
            if firstguess >= 10000:
               break
            pbar.update(1)
    print("""{
    "depth": %s,
    "score": %s,
    "move": "%s",
    "time_took": %s,
    "nodes searched": %s,
    "nodes per second": %s}""" % (depth, value, best_move, time() - st, nodesss, nodesss/(time() - st)))
    tomove = False
    return best_move

nodesss = 0

def nodes(num_to_increase = 0):
   global nodesss
   nodesss += num_to_increase
   return nodesss, nodesss / (time() - st)

nodesss = 0

def order(board):
   for move in board.legal_moves:
      pass

def negamax(board, depth, alpha, beta, color):
   global nodesss
   """
   color can be either 1 or -1. 1 for white and -1 for black
   """
   possible = tuple(board.legal_moves)
   if depth == 0 or len(possible) == 0:
      #quiesced = quiesce(-10000, 10000, board, 1) * -color
      return evaluate(board.fen()) * -color #quiesced
   value = -inf
   for move in possible:
      nodesss += 1
      #nodes(1)   
      #assert board.is_legal(move)
      board.push(move)
      nega = -negamax(board, depth-1, alpha, beta, -color)

      board.pop()
      value = max(value, nega) # can do class node that stores a board and the possible moves and then I can say if it is a terminal node (a node that determains loss, win and draws) with a parameter purpose.
      alpha = max(alpha, value)
      if alpha >= beta:
         break

   return value

def quiesce(alpha,beta, board, depth):
   captures = tuple(board.generate_pseudo_legal_captures())
   stand_pat = evaluate(board.fen())
   if( stand_pat >= beta ): return beta
   alpha = max(stand_pat, alpha)
   if depth == 0: return stand_pat
   for capture in captures:
      #nodes(1)
      board.push(capture)
      score = -quiesce( -beta, -alpha, board, depth - 1 )
      board.pop()
      if( score >= beta ): return beta
      alpha = max(alpha, score)
   return alpha

def main2():
   opt = sys.argv
   board = chess.Board(opt[1])
   depth = int(opt[2])
   color = int(opt[3])
   negaa = njit(negamax_root, nogil=True)
   return negamax_root(board, depth, -inf, 10000, color)

if __name__=="__main__":
   main2()
esc commented 4 years ago

@OfekShochat thanks for following up. I think part of the problem here is that Numba doesn't know how to handle tqdm and potentially also some of the other constructs you are using. Additionally, Numba is more of a function compiler, rather than a full-program compiler and a common use case is to identify bottleneck functions and to compile those. My suggestion would be to start with the 5 min guide to Numba, which you can find here:

https://numba.readthedocs.io/en/stable/user/5minguide.html

and then look at the supported Python constructs here:

https://numba.readthedocs.io/en/stable/reference/pysupported.html

OfekShochat commented 4 years ago

thanks!

esc commented 4 years ago

@OfekShochat another thought: since you seem to be developing something educational/fun it might also be worth opening a thread on the Numba discourse here: https://numba.discourse.group/ -- perhaps other members of our community will find this interesting and join in on a discussion about this subject?

OfekShochat commented 4 years ago

sure!

esc commented 4 years ago

I am closing this issue now, since I believe it has been resolved. As a side-note: Numba is currently unable to handle with ... as ... statements and this leads to the error message.

esc commented 4 years ago

Probably we can catch the error message and give you a better message along the lines of: "Numba is currently unable to compile your code."