1

I am working in Cython. How can i declare a C array of a python class instances and then pass the array to a python function and work on it?

cdef int n=100
class particle:
    def __init__(self):
        self.x=uniform(1,99)
        self.y=uniform(1,99)
        self.pot=0
cdef particle parlist[n]
def CalPot(parlist[]):
    for i in range(N-1):
        pot=0
        for j in range(i,N):
            dx=parlist[j].x-parlist[i].x
            dy=parlist[j].y-parlist[j].y
            r2=dx**2+dy**2
            pot=pot+4*ep*r2*((sig/r2)**12 - (sig/r2)**6)
        parlist[i].pot=pot
Coldz
  • 33
  • 5
  • Very similar question: https://stackoverflow.com/questions/33851333/cython-how-do-you-create-an-array-of-cdef-class. The answer is still "you don't" – DavidW Oct 25 '17 at 16:52

2 Answers2

0

BUG: use spaces, not tabs, REF: use fenced code block in Markdown

Instances of Python classes are Python objects, and better handled in Python (they are not C types, and I don't see any reason for creating some form of C representation for them within the Cython source). Also, global variables like n and parlist are better avoided (in this example they aren't necessary).

class particle:
    def __init__(self):
        self.x = uniform(1, 99)
        self.y = uniform(1, 99)
        self.pot = 0


def CalPot(parlist):
    N = len(parlist)
    for i in range(N):
        pot = 0
        for j in range(i, N):
            dx = parlist[j].x - parlist[i].x
            dy = parlist[j].y - parlist[j].y
            r2 = dx**2 + dy**2
            pot = pot + 4 * ep * r2 * ((sig / r2)**12 - (sig / r2)**6)
        parlist[i].pot = pot

So this Cython code happens to be pure Python.

0 _
  • 10,524
  • 11
  • 77
  • 109
0

As Ioannis and DavidW told you, you should not create a c-array of python objects and should use a python list instead.

Cytonizing the resulting pure Python would bring a speed up of about factor 2, because cython would cut out the interpreter part. However, there is much more potential if you would also get rid of reference counting and dynamic dispatch - a speed-up up to factor 100 is pretty common. Some time ago I answered a question illustrating this.

What should you do to get this speed up? You need to replace python-multiplication with "bare metal" multiplications.

First step: Don't use a (python)-class for particle, use a simple c-struct - it is just a collection of data - nothing more, nothing less:

cdef struct particle:
   double x
   double y
   double pot

First benefit: It is possible to define a global c-array of these structs (another question is, whether it is a very smart thing to do in a bigger project):

DEF n=2000 # known at compile time
cdef particle parlist[n] 

After the initialization of the array (for more details see attached listings), we can use it in our calcpot-function (I slightly changed your definition):

def calcpot():
   cdef double pot,dX,dY
   cdef int i,j
   for i in range(n):
     pot=0.0
     for j in range(i+1, n):
         dX=parlist[i].x-parlist[j].x
         dY=parlist[i].y-parlist[j].y
         pot=pot+1.0/(dX*dX+dY*dY)
     parlist[i].pot=pot

The main difference to the original code: parlist[i].xand Co. are no longer slow python objects but simple and fast doubles. There are a lot of subtle things to be considered in order to be able to get the maximal speed-up - one really should read/reread the cython documentation.

Was the trouble worth it? There are the timings(via %timeit calcpot()) on my machine:

                                  Time            Speed-up
pure python + interpreter:   924 ms ± 14.1 ms       x1.0
pure python + cython:        609 ms ± 6.83 ms       x1.5
cython version:               4.1 ms ± 55.3 µs     x231.0

A speed up of 231 through using the lowly stucts!


Listing python code:

import random

class particle:
    def __init__(self):
        self.x=random.uniform(1,99)
        self.y=random.uniform(1,99)
        self.pot=0

n=2000
parlist = [particle() for _ in range(n)]

def calcpot():
     for i in range(n):
       pot=0.0
       for j in range(i+1, n):
         dX=parlist[i].x-parlist[j].x
         dY=parlist[i].y-parlist[j].y
         pot=pot+1.0/(dX*dX+dY*dY)
       parlist[i].pot=pot

Listing cython code:

#call init_parlist prior to calcpot!
cdef struct particle:
   double x
   double y
   double pot

DEF n=2000 # known at compile time
cdef particle parlist[n] 

import random
def init_parlist():
  for i in range(n):
     parlist[i].x=random.uniform(1,99)
     parlist[i].y=random.uniform(1,99)
     parlist[i].pot=0.0

def calcpot():
   cdef double pot,dX,dY
   cdef int i,j
   for i in range(n):
     pot=0.0
     for j in range(i+1, n):
         dX=parlist[i].x-parlist[j].x
         dY=parlist[i].y-parlist[j].y
         pot=pot+1.0/(dX*dX+dY*dY)
     parlist[i].pot=pot
ead
  • 32,758
  • 6
  • 90
  • 153