高速化(Cython化)

Pythonで個別要素法プログラムの作成



高速化

DEMソルバーをCythonで作成し、高速化します。生Pythonに比べて大体40倍くらい早くなります。

DEMソルバー(Cython)

Cythonのソースコードです。
コンパイル済みのpydファイル(64bit)をダウンロードしたい場合は、コチラ

  1. # -*- coding: utf-8 -*-
  2.  
  3. from libc.math cimport sqrt,sin,cos,atan2,fabs
  4. from cpython cimport bool
  5. from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
  6.  
  7. cdef double PI = 3.1415926535 #円周率
  8. cdef double G = 9.80665 #重力加速度
  9.  
  10. cdef struct Particle:
  11.  
  12. #要素共通
  13. int etype #要素タイプ
  14. int n #要素No.
  15. double r #半径
  16. double x #X座標
  17. double y #Y座標
  18. double a #角度
  19. double dx #X方向増加量
  20. double dy #Y方向増加量
  21. double da #角度増加量
  22. double vx #X方向速度
  23. double vy #Y方向速度
  24. double va #角速度
  25. double fy
  26. double fx
  27. double fm
  28. double* en #弾性力(直方向)
  29. double* es #弾性力(せん断方向)
  30.  
  31. #粒子専用
  32. double m #質量
  33. double Ir #慣性モーメント
  34.  
  35. cdef struct Line:
  36.  
  37. #要素共通
  38. int etype #要素タイプ
  39. int n #要素No.
  40. double r #半径
  41. double x #X座標
  42. double y #Y座標
  43. double a #角度
  44. double dx #X方向増加量
  45. double dy #Y方向増加量
  46. double da #角度増加量
  47. double vx #X方向速度
  48. double vy #Y方向速度
  49. double va #角速度
  50. double fy
  51. double fx
  52. double fm
  53.  
  54. #線要素専用
  55. double x1
  56. double y1
  57. double x2
  58. double y2
  59.  
  60. cdef struct Interface:
  61. double kn #弾性係数(法線方向)
  62. double etan #粘性係数(法線方向)
  63. double ks #弾性係数(せん断方向)
  64. double etas #弾性係数(せん断方向)
  65. double frc #摩擦係数
  66.  
  67. #グローバル変数
  68. cdef Particle* pe
  69. cdef Line* le
  70. cdef Interface infs[9]
  71. cdef int int_no[9][9]
  72. cdef int area[4] # 解析範囲
  73. cdef int pe_count #粒子の数
  74. cdef int le_count #線要素の数
  75. cdef double dt = 0.001 #計算間隔
  76. cdef int st = 0 #ステップ
  77.  
  78. cdef double rho = 10 #粒子間密度
  79.  
  80. cdef void next_step():
  81. global st
  82. reset_force()
  83. calc_force()
  84. update_coord()
  85. st += 1
  86.  
  87. cdef void reset_force():
  88. cdef int i
  89. for i in range(pe_count):
  90. pe[i].fx = 0
  91. pe[i].fy = 0
  92. pe[i].fm = 0
  93.  
  94. cdef Interface interface(int etype1,int etype2):
  95. cdef Interface inf
  96. cdef int n
  97. n = int_no[etype1][etype2]
  98. inf.kn = infs[n].kn
  99. inf.etan = infs[n].etan
  100. inf.ks = infs[n].ks
  101. inf.etas = infs[n].etas
  102. inf.frc = infs[n].frc
  103. return inf
  104.  
  105. cdef void calc_force():
  106. #2粒子間の接触判定
  107. cdef double lx,ly,ld,cos_a,sin_a
  108. cdef int i,j,n
  109. for i in range(pe_count):
  110. for j in range(i+1, pe_count):
  111. lx = pe[j].x - pe[i].x
  112. ly = pe[j].y - pe[i].y
  113. ld = (lx**2+ly**2)**0.5
  114.  
  115. if (pe[i].r+pe[j].r)>ld:
  116. cos_a = lx/ld
  117. sin_a = ly/ld
  118. calc_force_par2par(i,j,cos_a,sin_a)
  119. else:
  120. pe[i].en[j] = 0.0
  121. pe[i].es[j] = 0.0
  122.  
  123. #粒子と線の接触判定
  124. cdef bool hit
  125. cdef double x,y,a,d,b,s
  126. for i in range(pe_count):
  127. for j in range(le_count):
  128. hit = False
  129. th0 = atan2(le[j].y2-le[j].y1, le[j].x2-le[j].x1)
  130. th1 = atan2(pe[i].y -le[j].y1, pe[i].x -le[j].x1)
  131. a = sqrt((pe[i].x-le[j].x1)**2+(pe[i].y-le[j].y1)**2)
  132. d = fabs(a*sin(th1-th0))
  133. if d < pe[i].r:
  134. b = sqrt((pe[i].x -le[j].x2)**2+(pe[i].y -le[j].y2)**2)
  135. s = sqrt((le[j].x2-le[j].x1)**2+(le[j].y2-le[j].y1)**2)
  136. s = sqrt(s**2 + pe[i].r**2)
  137. if a < s and b < s:
  138. s = sqrt(a**2-d**2)
  139. x = le[j].x1 + s*cos(th0)
  140. y = le[j].y1 + s*sin(th0)
  141. hit = True
  142. elif a < b and a < pe[i].r:
  143. x = le[j].x1
  144. y = le[j].y1
  145. hit = True
  146. elif b < pe[i].r:
  147. x = le[j].x2
  148. y = le[j].y2
  149. hit = True
  150. if hit:
  151. lx = x - pe[i].x
  152. ly = y - pe[i].y
  153. ld = sqrt(lx**2+ly**2)
  154. cos_a = lx/ld
  155. sin_a = ly/ld
  156. calc_force_line2par(i,le[j].n,2,cos_a,sin_a)
  157. else:
  158. pe[i].en[le[j].n] = 0.0
  159. pe[i].es[le[j].n] = 0.0
  160. #外力
  161. for i in range(pe_count):
  162. pe[i].fy += -G*pe[i].m #重力
  163.  
  164. cdef void calc_force_par2par(int i,int j,double cos_a,double sin_a):
  165. cdef double un,us,vn,vs,hn,hs
  166. cdef Interface inf
  167.  
  168. #相対的変位増分
  169. un = +(pe[i].dx-pe[j].dx)*cos_a+(pe[i].dy-pe[j].dy)*sin_a
  170. us = -(pe[i].dx-pe[j].dx)*sin_a+(pe[i].dy-pe[j].dy)*cos_a+(pe[i].r*pe[i].da+pe[j].r*pe[j].da)
  171.  
  172. inf = interface(pe[i].etype,pe[j].etype)
  173.  
  174. #合力(局所座標系)
  175. pe[i].en[j] += inf.kn*un
  176. pe[i].es[j] += inf.ks*us
  177. hn = pe[i].en[j] + inf.etan*un/dt
  178. hs = pe[i].es[j] + inf.etas*us/dt
  179.  
  180. if hn <= 0.0:
  181. #法線力がなければ、せん断力は0
  182. hs = 0.0
  183. elif fabs(hs) >= inf.frc*hn:
  184. #摩擦力以上のせん断力は働かない
  185. hs = inf.frc*fabs(hn)*hs/fabs(hs)
  186.  
  187. #全体合力(全体座標系)
  188. pe[i].fx += -hn*cos_a + hs*sin_a
  189. pe[i].fy += -hn*sin_a - hs*cos_a
  190. pe[i].fm -= pe[i].r*hs
  191. pe[j].fx += hn*cos_a - hs*sin_a
  192. pe[j].fy += hn*sin_a + hs*cos_a
  193. pe[j].fm -= pe[j].r*hs
  194.  
  195. cdef void calc_force_line2par(int i,int ln,int mat,double cos_a, double sin_a):
  196. cdef double un,us,vn,vs,hn,hs
  197. cdef Interface inf
  198.  
  199. #相対的変位増分
  200. un = +pe[i].dx*cos_a+pe[i].dy*sin_a
  201. us = -pe[i].dx*sin_a+pe[i].dy*cos_a+pe[i].r*pe[i].da
  202. #相対的速度増分
  203. vn = +pe[i].vx*cos_a+pe[i].vy*sin_a
  204. vs = -pe[i].vx*sin_a+pe[i].vy*cos_a+pe[i].r*pe[i].va
  205.  
  206. inf = interface(pe[i].etype,mat)
  207.  
  208. #合力(局所座標系)
  209. pe[i].en[ln] += inf.kn*un
  210. pe[i].es[ln] += inf.ks*us
  211. hn = pe[i].en[ln] + inf.etan*vn
  212. hs = pe[i].es[ln] + inf.etas*vs
  213.  
  214. if hn <= 0.0:#法線力がなければ、せん断力は0
  215. hs = 0.0
  216. elif fabs(hs) >= inf.frc*hn:#摩擦力以上のせん断力は働かない
  217. hs = inf.frc*fabs(hn)*hs/fabs(hs)
  218.  
  219. #全体合力(全体座標系)
  220. pe[i].fx += -hn*cos_a + hs*sin_a
  221. pe[i].fy += -hn*sin_a - hs*cos_a
  222. pe[i].fm -= pe[i].r*hs
  223.  
  224. cdef void update_coord():
  225. cdef double ax,ay,aa
  226. cdef int i
  227. for i in range(pe_count):
  228. #位置更新(オイラー差分)
  229. ax = pe[i].fx/pe[i].m
  230. ay = pe[i].fy/pe[i].m
  231. aa = pe[i].fm/pe[i].Ir
  232. pe[i].vx += ax*dt
  233. pe[i].vy += ay*dt
  234. pe[i].va += aa*dt
  235. pe[i].dx = pe[i].vx*dt
  236. pe[i].dy = pe[i].vy*dt
  237. pe[i].da = pe[i].va*dt
  238. pe[i].x += pe[i].dx
  239. pe[i].y += pe[i].dy
  240. pe[i].a += pe[i].da
  241.  
  242. # -------------------------
  243. # Pythonからの設定用
  244. # -------------------------
  245.  
  246. def set_delta_time(sec):
  247. global dt
  248. dt = sec
  249.  
  250. def set_number_of_particle(n):
  251. global pe_count,pe
  252. pe_count = n
  253. pe = <Particle*> PyMem_Malloc(n * sizeof(Particle))
  254. if not pe:
  255. raise MemoryError()
  256.  
  257. def set_particle(pe_no,pe_obj):
  258. pe[pe_no].x = pe_obj.x
  259. pe[pe_no].y = pe_obj.y
  260.  
  261. def particle(pe_no,pe_obj):
  262. pe_obj.x = pe[pe_no].x
  263. pe_obj.y = pe[pe_no].y
  264. pe_obj.a = pe[pe_no].a
  265. return pe_obj
  266.  
  267. def set_number_of_line(n):
  268. global le_count,le
  269. le_count = n
  270. le = <Line*> PyMem_Malloc(n * sizeof(Line))
  271. if not le:
  272. raise MemoryError()
  273.  
  274. def set_line(l_no,l_obj):
  275. le[l_no].x1 = l_obj.x1
  276. le[l_no].y1 = l_obj.y1
  277. le[l_no].x2 = l_obj.x2
  278. le[l_no].y2 = l_obj.y2
  279.  
  280. def line(l_no,l_obj):
  281. l_obj.x1 = le[l_no].x1
  282. l_obj.y1 = le[l_no].y1
  283. l_obj.x2 = le[l_no].x2
  284. l_obj.y2 = le[l_no].y2
  285. return l_obj
  286.  
  287. def set_area(x_min,x_max,y_min,y_max):
  288. global area
  289. area[0] = x_min
  290. area[1] = x_max
  291. area[2] = y_min
  292. area[3] = y_max
  293.  
  294. def set_interface(mat1,mat2,inf_no,inf_obj):
  295. int_no[mat1][mat2] = inf_no
  296. infs[inf_no].kn = inf_obj.kn
  297. infs[inf_no].etan = inf_obj.etan
  298. infs[inf_no].ks = inf_obj.ks
  299. infs[inf_no].etas = inf_obj.etas
  300. infs[inf_no].frc = inf_obj.frc
  301.  
  302. def initialize():
  303. global pe,le
  304. cdef int i,j,n
  305. n = pe_count + le_count + 4
  306. for i in range(pe_count):
  307. pe[i].etype = 1
  308. pe[i].n = i
  309. pe[i].x = 0
  310. pe[i].y = 0
  311. pe[i].r = 0
  312. pe[i].a = 0
  313. pe[i].dx = 0
  314. pe[i].dy = 0
  315. pe[i].da = 0
  316. pe[i].vx = 0
  317. pe[i].vy = 0
  318. pe[i].va = 0
  319. pe[i].fx = 0
  320. pe[i].fy = 0
  321. pe[i].fm = 0
  322. pe[i].m = 0
  323. pe[i].Ir = 0
  324. pe[i].en = <double*> PyMem_Malloc(n * sizeof(double))
  325. pe[i].es = <double*> PyMem_Malloc(n * sizeof(double))
  326. for j in range(n):
  327. pe[i].en[j] = 0
  328. pe[i].es[j] = 0
  329. for i in range(le_count):
  330. le[i].etype = 2
  331. le[i].n = pe_count+i
  332.  
  333. def setup():
  334. global pe,le
  335. cdef int i
  336. #粒子要素
  337. for i in range(pe_count):
  338. pe[i].r = 5.0
  339. pe[i].m = 4.0/3.0*PI*rho*pe[i].r**3 # 質量
  340. pe[i].Ir = PI*rho*pe[i].r**4/2.0 #慣性モーメント
  341. #線要素
  342.  
  343. def step():
  344. return st
  345.  
  346. def calc_step(int n=1):
  347. cdef int i
  348. for i in range(n):
  349. next_step()

ユーザーインターフェイス(Python)

Cythonで作成したDEMソルバーを動かすPythonのソースコードです。
実行すると、100個以上のの球がコロコロ転がる描写が実行させるはずです。

  1. # -*- coding: utf-8 -*-
  2.  
  3. print('読み込み中...',end='')
  4.  
  5. import sys
  6. import math
  7. import random
  8. import time
  9. import tkinter
  10.  
  11. import dem
  12.  
  13. class Element(object):
  14. def __init__(self):
  15. self.n = 0 #要素No.
  16. self.r = 0 #半径
  17. self.x = 0 #X座標
  18. self.y = 0 #Y座標
  19. self.a = 0 #角度
  20. self.dx = 0 #X方向増加量
  21. self.dy = 0 #Y方向増加量
  22. self.da = 0 #角度増加量
  23. self.vx = 0 #X方向速度
  24. self.vy = 0 #Y方向速度
  25. self.va = 0 #角速度
  26. self.fy = 0
  27. self.fx = 0
  28. self.fm = 0
  29.  
  30. self.en = [] #弾性力(直方向)
  31. self.es = [] #弾性力(せん断方向)
  32.  
  33. class Particle(Element):
  34. def __init__(self,x=0,y=0,vx=0,vy=0):
  35. super(Particle,self).__init__()
  36. self.type = 1
  37.  
  38. self.x = x #X座標
  39. self.y = y #Y座標
  40. self.vx = vx #X方向速度
  41. self.vy = vy #Y方向速度
  42.  
  43. rho = 10
  44. self.r = 5 #半径
  45. self.m = 4.0/3.0*math.pi*rho*self.r**3 # 質量
  46. self.loop_nr = math.pi*rho*self.r**4/2.0 #慣性モーメント
  47.  
  48. class Line(Element):
  49. def __init__(self,x1=0,y1=0,x2=0,y2=0):
  50. super(Line,self).__init__()
  51. self.type = 2
  52. self.x1 = x1
  53. self.y1 = y1
  54. self.x2 = x2
  55. self.y2 = y2
  56.  
  57. class Interface(object):
  58. def __init__(self):
  59. self.kn = 0 #弾性係数(法線方向)
  60. self.etan = 0 #粘性係数(法線方向)
  61. self.ks = 0 #弾性係数(せん断方向)
  62. self.etas = 0 #粘性係数(せん断方向)
  63. self.frc = 0 #摩擦係数
  64.  
  65. class DEM_UI:
  66. def __init__(self):
  67. # setup
  68. self.area = [5,295,5,195]
  69.  
  70. # lines
  71. lines = []
  72. lines.append(Line(100,100,300,150))
  73. lines.append(Line(10,80,160,50))
  74. a = self.area
  75. lines.append(Line(a[0],a[2],a[1],a[2]))
  76. lines.append(Line(a[0],a[3],a[1],a[3]))
  77. lines.append(Line(a[0],a[2],a[0],a[3]))
  78. lines.append(Line(a[1],a[2],a[1],a[3]))
  79. self.line_count = len(lines)
  80.  
  81. # particle
  82. pars = []
  83. for x in range(40,290,2):
  84. for y in range(145,190,2):
  85. if self._is_touch_to_particles(x,y,5,pars):
  86. continue
  87. if self._is_touch_to_lines(x,y,5,lines):
  88. continue
  89. pars.append(Particle(x,y))
  90. self.par_count = len(pars)
  91.  
  92. # interface
  93. inf = [[Interface() for i in range(9)] for j in range(9)]
  94. #粒子同士
  95. inf[1][1].kn = 100000 #弾性係数(法線方向)
  96. inf[1][1].etan= 5000 #粘性係数(法線方向)
  97. inf[1][1].ks = 5000 #弾性係数(せん断方向)
  98. inf[1][1].etas= 1000 #粘性係数(せん断方向)
  99. inf[1][1].frc = 10 #摩擦係数
  100. #粒子と線要素
  101. inf[1][2].kn = 500000
  102. inf[1][2].etan= 10000
  103. inf[1][2].ks = 1000
  104. inf[1][2].etas= 900
  105. inf[1][2].frc = 1
  106.  
  107. #setup dem
  108. dem.set_delta_time(0.01)
  109. dem.set_area(*self.area)
  110. dem.set_number_of_particle(self.par_count)
  111. dem.set_number_of_line(len(lines))
  112. dem.initialize()
  113. for i,l in enumerate(lines):
  114. dem.set_line(i,l)
  115. for i,p in enumerate(pars):
  116. dem.set_particle(i,p)
  117. dem.set_interface(1,1,0,inf[1][1])
  118. dem.set_interface(1,2,1,inf[1][2])
  119. dem.setup()
  120.  
  121. def _is_touch_to_particles(self,x,y,r,pars):
  122. hit = False
  123. for p in pars:
  124. lx = p.x - x
  125. ly = p.y - y
  126. ld = (lx**2+ly**2)**0.5
  127. if (p.r+r)>=ld:
  128. hit = True
  129. break
  130. return hit
  131.  
  132. def _is_touch_to_lines(self,px,py,pr,lines):
  133. hit = False
  134. for l in lines:
  135. th0 = math.atan2(l.y2-l.y1,l.x2-l.x1)
  136. th1 = math.atan2(py-l.y1,px-l.x1)
  137. a = math.sqrt((px-l.x1)**2+(py-l.y1)**2)
  138. d = abs(a*math.sin(th1-th0))
  139. if d < pr:
  140. b = math.sqrt((px-l.x2)**2+(py-l.y2)**2)
  141. s = math.sqrt((l.x2-l.x1)**2+(l.y2-l.y1)**2)
  142. if a < s and b < s:
  143. hit = True
  144. elif a < b and a < pr:
  145. hit = True
  146. elif b < pr:
  147. hit = True
  148. if hit:
  149. break
  150. return hit
  151.  
  152. def particles(self):
  153. pars = []
  154. for i in range(self.par_count):
  155. p = dem.particle(i,Particle())
  156. pars.append(p)
  157. return pars
  158.  
  159. def lines(self):
  160. lines = []
  161. for i in range(self.line_count):
  162. l = dem.line(i,Line())
  163. lines.append(l)
  164. return lines
  165.  
  166.  
  167. class Window(tkinter.Tk):
  168.  
  169. def __init__(self):
  170. self.loop_n = 0
  171.  
  172. print('初期設定中...',end='')
  173. super().__init__()
  174. self.canvas = tkinter.Canvas(self, bg="white")
  175. self.canvas.pack(fill=tkinter.BOTH,expand=True)
  176. self.geometry('300x200')
  177. self.title('DEM')
  178.  
  179. self.dem_ui = DEM_UI()
  180. for l in self.dem_ui.lines():
  181. xy = self.view_coord([l.x1,l.y1,l.x2,l.y2])
  182. self.canvas.create_line(xy,width=1)
  183.  
  184. self.redraw()
  185. self.update_idletasks()
  186.  
  187. print('完了')
  188. print('粒子要素数: %d ' % self.dem_ui.par_count)
  189. print('解析開始')
  190.  
  191. def calcloop(self):
  192. if self.loop_n % 10 == 0:
  193. print('Step %d' % dem.step())
  194. if self.loop_n % 1 == 0:
  195. self.redraw()
  196. if dem.step() >= 5000:
  197. print('解析終了.設定最大ループに達しました')
  198. else:
  199. self.after(0,self.calcloop)
  200. dem.calc_step(10)
  201. self.update_idletasks()
  202. self.loop_n += 1
  203.  
  204. def redraw(self):
  205. self.canvas.delete('elem')
  206. h = 200
  207. for p in self.dem_ui.particles():
  208. x1,y1 = self.view_coord([p.x-p.r,p.y-p.r])
  209. x2,y2 = self.view_coord([p.x+p.r,p.y+p.r])
  210. self.canvas.create_oval(x1,y1,x2,y2,tags='elem')
  211.  
  212. x1,y1 = self.view_coord([p.x,p.y])
  213. x2,y2 = self.view_coord([p.x+p.r*math.cos(p.a),
  214. p.y+p.r*math.sin(p.a)])
  215. self.canvas.create_line(x1,y1,x2,y2,tags='elem')
  216.  
  217. def view_coord(self,coords,offset=(0,0)):
  218. s = 1.0 # 表示倍率
  219. h = 200 #表示画面高さ
  220. w = 300 #表示画面幅
  221. x_offset = 0
  222. y_offset = 0
  223. xy_list = []
  224. for i in range(0,len(coords),2):
  225. x = round(s*coords[i])+x_offset
  226. y = round(h-s*coords[i+1])-y_offset
  227. x = x + offset[0]
  228. y = y + offset[1]
  229. xy_list.append(x)
  230. xy_list.append(y)
  231. return xy_list
  232.  
  233. def main():
  234. w = Window()
  235. w.after(0,w.calcloop)
  236. w.mainloop()
  237.  
  238. print('完了') #読み込み
  239.  
  240. if __name__ == '__main__':
  241. main()
ページトップへ