title: Why this code can't be parallelised with Numba?
body: <p>I am working on a physics simulation. I am actually trying to implement a cluster Wolff algorithm for the Ising model. This deals with mainly manipulation of NumPy arrays on the programming side. I want to make a plot of specific heat,energy, magnetization with temperature. For different temperatures, the simulations are entirely independent. So,I tried to parallelize the operation with the numba prange function. But it is not allowing it. The reason seems another function that I am calling from my main function.</p>
  
  <p>My code is here:</p>
  
  <pre class="lang-py prettyprint-override"><code># -*- coding: utf-8 -*-
  """
  Created on Mon Mar 30 18:39:45 2020
  
  @author: ENDEAVOUR
  """
  
  #%matplotlib inline
  import numpy as np
  import matplotlib.pyplot as plt
  from numba import jit,prange
  import timeit
  #----------------------------------------------------------------------
  ##  semi-block codes################
  #----------------------------------------------------------------------
  
  start = timeit.default_timer()
  error_model=True
  
  @jit(error_model="numpy",nopython=True)
  def initialstate(N):   
      ''' generates a random spin configuration for initial condition'''
      state =2*np.random.randint(0,2, size=(N,N))-1
      return state
  
  @jit(error_model="numpy",nopython=True)
  def calcEnergy(config):
      '''Energy of a given configuration'''
      N=len(config)
      energy = 0
      for i in range(len(config)):
          for j in range(len(config)):
                 S = config[i,j]
                 nb = config[(i+1)%N, j] + config[i,(j+1)%N]  + config[(i-1)%N, j] + config[i,(j-1)%N] 
                 energy += -nb*S - H*S
      return energy/(4.)
  
  @jit(error_model="numpy",nopython=True)
  def calcMag(config):
      '''Magnetization of a given configuration'''
      mag = np.sum(config) + H
      return mag
  
  
  def wolff(config,p):##wollff cluster implementation 
  
     ## the followng is the function for a sngle run of cluster making and flipping.
  
  
     #print(config)
     '''Monte Carlo move using Wolff-Cluster Sampling'''
  
     que = []  ### "que" ; stores the list of coordinates of the neighbours aded to the cluster
     (x0,y0) = np.random.randint(len(config)),np.random.randint(len(config)) ## a random spin is chosen at first
     que.append((x0,y0)) ## then added to the "que"
  
  
     while (len(que) &gt; 0):## as mentioned in the documents I havesnt u , code must run untill there is nobody left to be added
  
         #print('Length of que ',len(que))
  
         q = que[np.random.randint(len(que))] ## a random element from que is chosen
  
         neighbours = [((q[0]+1)%N,q[1]), ((q[0]-1)%N,q[1]), (q[0],(q[1]+1)%N), (q[0],(q[1]-1)%N) ] ## neighbours to the selected spin are considered
         for c in neighbours:
  
             if all([config[q]==config[c]  , c not in que,np.random.rand() &lt; p]):
  ## process of adding spins to the que based on wolff's criteria if they have same spin
                que.append(c)
  
  
         config[q] *= -1 ## the spin'q' that was selected from the que is flipped so to avoid being selected in future
         que.remove(q)  ## the spin'q' is removed from the 'que'
  
     return config
  
  
  
  @jit(error_model="numpy",parallel=True)
  def run_simulation(N,eqSteps,mcSteps,T,nt):
  
      E,M,C,X = np.empty(nt), np.empty(nt), np.empty(nt), np.empty(nt)
  
      n1, n2  = 1.0/(mcSteps*N*N), 1.0/(mcSteps*mcSteps*N*N) 
  
  
      for tt in prange(nt):
  
          E1 = M1 = E2 = M2 = 0
          config = initialstate(N)
          iT=1.0/T[tt]; iT2=iT*iT;
          p = 1 - np.exp(-2*J*iT)
  
          for i in range(eqSteps):         # equilibrate
              config=wolff(config,p)                # Monte Carlo moves
  
  
  
          for i in range(mcSteps):
              config=wolff(config,p)            
              Ene = calcEnergy(config)     # calculate the energy
              Mag = abs(calcMag(config))       # calculate the magnetisation
  
  
  
              E1 = E1 + Ene
              M1 = M1 + Mag
              M2 = M2 + Mag*Mag 
              E2 = E2 + Ene*Ene
  
  
          E[tt] = n1*E1
          M[tt] = n1*M1
          C[tt] = (n1*E2 - n2*E1*E1)*iT2
          X[tt] = (n1*M2 - n2*M1*M1)*iT
  
          print ("Temp:",T[tt],":", E[tt], M[tt],C[tt],X[tt])
  
  
      return E,M,C,X
  
  #def control():
  ####################################
  N  = 64       #N X N spin field
  J  = 1
  H  = 0
  nt = 50
  eqSteps = 150
  mcSteps = 20
  
  #############################################No change rquired here
  T  = np.linspace(1.33, 4.8, nt) 
  E,M,C,X=run_simulation(N,eqSteps,mcSteps,T,nt)
  
  
  
  f = plt.figure(figsize=(25, 20)); # plot the calculated values    
  plt.title("External Field :%5.2f"%(H))
  sp =  f.add_subplot(3, 2, 1 );
  plt.scatter(T, E, s=50, marker='o', color='Red')
  plt.xlabel("Temperature (T)", fontsize=20);
  plt.ylabel("Energy ", fontsize=20);         plt.axis('tight');
  
  sp =  f.add_subplot(3, 2, 2 );
  plt.scatter(T, M, s=50, marker='o', color='Blue')
  plt.xlabel("Temperature (T)", fontsize=20); 
  plt.ylabel("Magnetization ", fontsize=20);   plt.axis('tight');
  
  sp =  f.add_subplot(3, 2, 3 );
  plt.scatter(T, C, s=50, marker='o', color='Red')
  plt.xlabel("Temperature (T)", fontsize=20);  
  plt.ylabel("Specific Heat ", fontsize=20);   plt.axis('tight');   
  
  sp =  f.add_subplot(3, 2, 4 );
  plt.scatter(T, X, s=50, marker='o', color='RoyalBlue')
  plt.xlabel("Temperature (T)", fontsize=20); 
  plt.ylabel("Susceptibility", fontsize=20);   plt.axis('tight');
  
  sp = f.add_subplot(3 ,2 ,5);
  plt.scatter(E, M,s=50, marker='+', color='Red') 
  plt.xlabel("Energy ", fontsize=20);         plt.axis('tight');
  plt.ylabel("Magnetization ", fontsize=20);   plt.axis('tight');
  
  plt.show()
  stop = timeit.default_timer()
  
  print('Time taken for this simulation:: ', stop - start)
  
  
  
  </code></pre>
  
  <p>The error message shown is</p>
  
  <pre class="lang-py prettyprint-override"><code>E:/Project/wolff_cluster.2.py:78: NumbaWarning: 
  Compilation is falling back to object mode WITH looplifting enabled because Function "run_simulation" failed type inference due to: Untyped global name 'wolff': cannot determine Numba type of &lt;class 'function'&gt;
  
  File "wolff_cluster.2.py", line 94:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
          for i in range(eqSteps):         # equilibrate
              config=wolff(config,p)                # Monte Carlo moves
              ^
  
    @jit(error_model="numpy",parallel=True,nopython=True)
  E:/Project/wolff_cluster.2.py:78: NumbaWarning: 
  Compilation is falling back to object mode WITHOUT looplifting enabled because Function "run_simulation" failed type inference due to: cannot determine Numba type of &lt;class 'numba.dispatcher.LiftedLoop'&gt;
  
  File "wolff_cluster.2.py", line 86:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
  
      for tt in prange(nt):
      ^
  
    @jit(error_model="numpy",parallel=True,nopython=True)
  C:\Users\Sandipan\Anaconda3\lib\site-packages\numba\compiler.py:725: NumbaWarning: Function "run_simulation" was compiled in object mode without forceobj=True, but has lifted loops.
  
  File "wolff_cluster.2.py", line 79:
  @jit(error_model="numpy",parallel=True,nopython=True)
  def run_simulation(N,eqSteps,mcSteps,T,nt):
  ^
  
    self.func_ir.loc))
  C:\Users\Sandipan\Anaconda3\lib\site-packages\numba\compiler.py:734: NumbaDeprecationWarning: 
  Fall-back from the nopython compilation path to the object mode compilation path has been detected, this is deprecated behaviour.
  
  For more information visit http://numba.pydata.org/numba-doc/latest/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit
  
  File "wolff_cluster.2.py", line 79:
  @jit(error_model="numpy",parallel=True,nopython=True)
  def run_simulation(N,eqSteps,mcSteps,T,nt):
  ^
  
    warnings.warn(errors.NumbaDeprecationWarning(msg, self.func_ir.loc))
  E:/Project/wolff_cluster.2.py:78: NumbaWarning: 
  Compilation is falling back to object mode WITHOUT looplifting enabled because Function "run_simulation" failed type inference due to: Untyped global name 'wolff': cannot determine Numba type of &lt;class 'function'&gt;
  
  File "wolff_cluster.2.py", line 94:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
          for i in range(eqSteps):         # equilibrate
              config=wolff(config,p)                # Monte Carlo moves
              ^
  
    @jit(error_model="numpy",parallel=True,nopython=True)
  C:\Users\Sandipan\Anaconda3\lib\site-packages\numba\compiler.py:725: NumbaWarning: Function "run_simulation" was compiled in object mode without forceobj=True.
  
  File "wolff_cluster.2.py", line 86:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
  
      for tt in prange(nt):
      ^
  
    self.func_ir.loc))
  C:\Users\Sandipan\Anaconda3\lib\site-packages\numba\compiler.py:734: NumbaDeprecationWarning: 
  Fall-back from the nopython compilation path to the object mode compilation path has been detected, this is deprecated behaviour.
  
  For more information visit http://numba.pydata.org/numba-doc/latest/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit
  
  File "wolff_cluster.2.py", line 86:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
  
      for tt in prange(nt):
      ^
  
    warnings.warn(errors.NumbaDeprecationWarning(msg, self.func_ir.loc))
  E:/Project/wolff_cluster.2.py:78: NumbaWarning: 
  Compilation is falling back to object mode WITHOUT looplifting enabled because Function "run_simulation" failed type inference due to: Untyped global name 'wolff': cannot determine Numba type of &lt;class 'function'&gt;
  
  File "wolff_cluster.2.py", line 94:
  def run_simulation(N,eqSteps,mcSteps,T,nt):
      &lt;source elided&gt;
          for i in range(eqSteps):         # equilibrate
              config=wolff(config,p)                # Monte Carlo moves
              ^
  
    @jit(error_model="numpy",parallel=True,nopython=True)
  Temp: 1.33 : -0.9890869140625 0.9942626953125 0.041807132522607905 0.017099021969053343
  
  </code></pre>
  
  <p>After showing the above error it runs as usual but takes 3x times compared to the pure python version. I tried without numba for Wolff, but no success. It is really important to parallelize the for loop in run_simulation as it is the most time-consuming part of the code. </p>
  
  <p>I think the problem is with Wolff function. Numba can't do anything to optimise it. But I have not used any function not supported by numba there. (simple list and numpy only). It marks the if condition--'all' keyword. I have tried with 'and' inside if ,but then the program runs into an infinite loop. It appends the same element multiple times in the que (probably overlooks the not in condition). I know this is technically impossible. (Both 'and' &amp; 'all' should work). I am doing something wrong. But I have no idea what it is.</p>
  
  <p>Can you please help me with optimization of this code. It works fine without numba. But with N >100,it will be too slow for any useful work.</p>