A have a problem with the speed of the program written in Python. The program is "Simulation of ferromagnetic particles in a magnetic field", and more specifically in the magnetically inert liquid. The program works, but very slowly compared to the same program written in C++, but I have it written in Python, on a project to study.
Overall, the program code is based on loops, with lots of floating point calculations. There is a random number of particles (generated randomly position) which interact with each other under the influence of a magnetic field.
This is the initial position:
http://i.stack.imgur.com/T15Bb.jpg
And the final:
http://i.stack.imgur.com/0nU5D.jpg
The main loop (in SymMain.py, with k variable) iterations are time steps are calculated coordinates of the particles in them at the time and the forces acting on it (the attractive force and a small repulsive force). In order to speed up, I wanted to use parallel processing to calculate the number of iterations at the same time, but this is not possible, because the calculation in one iteration depend on the calculations in the previous one.
I did not know that Python is so slower than C + +. As an example, the simulation of 529 particles in one time step is calculated (on my computer):
C + + ~ 0.5s
Python ~ 50s
So, how to speed up the program? Please help.
This code is only calculate, not drawing particles as in the pictures. The number of simulate partcile is on SymConst.py, this is nrH*nrL.
SymMain.py
#coding:windows-1250
from os import system
from SymCalc import *
from SymParticle import *
if __name__ == '__main__':
App = SymCalc()
App.MainLoop()
SymParticle.py
#coding:windows-1250
from random import randint
from math import *
from SymConst import *
from SymParticle import *
class SymCalc(object):
def __init__(self):
# declaration lists containing the properties of the particles
ParticleList = []
ParticleListTemp = []
t = 0.0
# the initial values of particle
for x in range(0, nParticle):
ParticleList.append(Particle(x+1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 8e-6))
ParticleListTemp.append(Particle(x+1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 8e-6))
# generating random coordinates X, Y
for x in range(0, nParticle):
self.Rand(ParticleList[x], x)
# time steps
for k in range(0, k0):
print "Time step = {0}".format(k)
# calculation of forces
for i in range(0, nParticle):
for j in range(0, i-1):
self.ParticleCalculate(ParticleList[i], ParticleList[j], 1)
for j in range(i+1, nParticle):
self.ParticleCalculate(ParticleList[i], ParticleList[j], 1)
# display data
if(k%Constant == 0):
for i in range(0, nParticle):
self.Print(k, i, ParticleList[i], t)
# changing the position of the particle
for i in range(0, nParticle):
self.ChangeOfPosition (ParticleList[i], dt)
# reset forces
for j in range(0, nParticle):
self.ParticleCalculate(ParticleList[i], ParticleList[j], 0)
self.ParticleCalculate(ParticleList[i], ParticleListTemp[j], 0)
# next time step
t += dt
# random coordinates of the particles
def Rand(self, part, lp):
l = lp%nrL # współrzędna X pola
part.x = (l-nrL/2)*(L0/nrL) + ((randint(0,32767)%100)-50)*(L0/nrL-2*part.r)/99 # X
h = (lp+1-(lp)%nrL)/nrL # Y
part.y = (h-nrH/2)*(H0/nrH) + ((randint(0,32767)%100)-50)*(H0/nrH-2*part.r)/99 # współrzędna Y cząstki
# calculating function of the force acting on the particles
def ParticleCalculate(self, part, part_tmp, p):
# auxiliary variables
# r_4, dx, dy, rr, sum, sx, sy, chi_eff, tmp, frepx, frepy, f0
md = []
if(p == 0):
part.frx = 0
part.fry = 0
part.fx = 0
part.fy = 0
if(p == 1):
# versor coordinates connecting the geometrical center of the particle
dx = part.x - part_tmp.x
dy = part.y - part_tmp.y
# the distance between the geometric means of the particle
rr = sqrt(dx*dx + dy*dy)
if(rr < 0.85*(part.r + part_tmp.r)):
print "ERROR: Invalid distance between the particles! Simulation aborted..."
if(rr >= 10*part.r):
# magnetic dipoles
chi_eff = (3.*(MI_P - 1.))/((MI_P - 1) + 3.)
md.append((4.*MI_0*pi*part.r*part.r*part.r*chi_eff*M_H0)/3.0)
md.append((4.*MI_0*pi*part_tmp.r*part_tmp.r*part_tmp.r*chi_eff*M_H0)/3.0)
tmp = pow(rr,7)
# first member
sum = (5.*(md[0]*part.nx*dx + md[0]*part.ny*dy) * (md[1]*part_tmp.nx*dx + md[1]*part_tmp.ny*dy)) / tmp
sx = sum*dx
sy = sum*dy
tmp = tmp / (rr*rr)
# second member
sum = (md[0]*part.nx*md[1]*part_tmp.nx + md[0]*part.ny*md[1]*part_tmp.ny) / tmp
sx -= sum*dx
sy -= sum*dy
# third member
sx -= (md[0]*(md[1]*part_tmp.nx*dx + md[1]*part_tmp.ny*dy)*part.nx + md[1]*(md[0]*part.nx*dx + md[0]*part.ny*dy)*part_tmp.nx)/tmp
sy -= (md[0]*(md[1]*part_tmp.nx*dx + md[1]*part_tmp.ny*dy)*part.ny + md[1]*(md[0]*part.nx*dx + md[0]*part.ny*dy)*part_tmp.ny)/tmp
# finally
tmp = (-3./(4*pi*MI_0))
sx *= tmp
sy *= tmp
part.fx += sx
part.fy += sy
# short-range repulsive force
tmp = pow(rr,15)
r_4 = pow((part.r+part.r),4)
f0 = 3.*fabs(md[0])*fabs(md[1])/(4.*pi*MI_0*r_4)
frepx = pow((part.r+part.r),15)*dx/(tmp*rr)*f0
frepy = pow((part.r+part.r),15)*dy/(tmp*rr)*f0
part.frx += frepx;
part.fry += frepy;
# change the position of the particle
def ChangeOfPosition(self, part, dt):
part.ddx = 0
part.ddy = 0
part.ddx = 1/(6*pi*part.r*eta)*(part.fx+part.frx)*dt
part.ddy = 1/(6*pi*part.r*eta)*(part.fy+part.fry)*dt
# particles new position value
part.x += part.ddx
part.y += part.ddy
if(part.x < -L0/2):
part.x += L0
elif(part.x > L0/2):
part.x -= L0
elif(part.y < -H0/2):
part.y += H0
elif(part.y > H0/2):
part.y -= H0
# display data
def Print(self, k, i, part, t):
print "-"*50
print "\nParticle {0}".format(i+1)
print "The resultant magnetostatic force fx = {0}".format(part.fx - part.frx)
print "The resultant magnetostatic force fy = {0}\n".format(part.fy - part.fry)
if(i == nParticle-1):
print "\n\t\t---t={0}[s]---".format(t)
print "-"*50
SymParticle.py
#coding:windows-1250
class Particle(object):
# generating a particle properties
def __init__(self, num, x, y, fx, fy, frx, fry, nx, ny, ddx, ddy, r):
self.num = num
self.x = x
self.y = y
self.fx = fx
self.fy = fy
self.frx = frx
self.fry = fry
self.nx = nx
self.ny = ny
self.ddx = ddx
self.ddy = ddy
self.r = r
SymConst.py
#coding:windows-1250
### Constant
M_H0 = 3e4
MI_0 = 12.56e-7
MI_P = 2000
eta = 0.1
k0 = 10001
dt = 1e-6 # time step
H0 = 5.95e-4
L0 = 5.95e-4
nrH = 4
nrL = 4
Constant = 100
nParticle = nrH*nrL # number of particle
To investigate this problem, first use the Python cProfile module to profile your code and find out where all of the time is being used (with print statements commented out):
$ python -m cProfile -s time SymMain.py
30264977 function calls in 34.912 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
7370737 27.547 0.000 30.501 0.000 SymCalc.py:62(ParticleCalculate)
1 3.626 3.626 34.909 34.909 SymCalc.py:8(__init__)
11101110 1.715 0.000 1.715 0.000 {math.pow}
160016 0.502 0.000 0.502 0.000 SymCalc.py:128(ChangeOfPosition)
4440476 0.498 0.000 0.498 0.000 {method 'append' of 'list' objects}
4440444 0.454 0.000 0.454 0.000 {math.fabs}
2250226 0.287 0.000 0.287 0.000 {math.sqrt}
500154 0.280 0.000 0.280 0.000 {range}
1 0.001 0.001 0.002 0.002 random.py:40(<module>)
1 0.001 0.001 0.001 0.001 hashlib.py:55(<module>)
1 0.001 0.001 0.003 0.003 SymCalc.py:2(<module>)
1616 0.000 0.000 0.000 0.000 SymCalc.py:151(Print)
1 0.000 0.000 34.912 34.912 SymMain.py:3(<module>)
32 0.000 0.000 0.000 0.000 SymParticle.py:4(__init__)
---8<--- snip ---8<---
In your case, most of the time is being spent in the SymCalc.py:62(ParticleCalculate)
function. Looking at that function it seems to be largely memory access and calculations. This is a good case for looking at PyPy!
PyPy is a fast, compliant alternative implementation of the Python language (2.7.3 and 3.2.3). It has several advantages and distinct features:
- Speed: thanks to its Just-in-Time compiler, Python programs often run faster on PyPy. (What is a JIT compiler?)
- Memory usage: large, memory-hungry Python programs might end up taking less space than they do in CPython.
- Compatibility: PyPy is highly compatible with existing python code. It supports cffi and can run popular python libraries like twisted and django.
- Sandboxing: PyPy provides the ability to run untrusted code in a fully secure way.
- Stackless: PyPy comes by default with support for stackless mode, providing micro-threads for massive concurrency.
- As well as other features.
$ time pypy SymMain.py
real 0m1.071s
user 0m1.025s
sys 0m0.042s
1.071s - Much better! Compare that to the original execution time on my system:
$ time python SymMain.py
real 0m29.766s
user 0m29.646s
sys 0m0.103s
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments