【发布时间】:2016-02-20 07:23:53
【问题描述】:
我想要一个用 numpy 编写的非常简单的弹簧系统。该系统将被定义为knots 的简单网络,由links 链接。我对随着时间的推移评估系统不感兴趣,但我想从初始状态开始,更改变量(通常将knot 移动到新位置)并求解系统直到它达到 stable 状态(最后施加的力低于给定阈值)。结没有质量,没有重力,力都来自每个链接的当前长度/初始长度。而唯一的“特殊”变量是每个结都可以设置为“锚定”(不动)。
所以我在下面编写了这个简单的求解器,并包含了一个简单的示例。跳到我的问题的最后。
import numpy as np
from numpy.core.umath_tests import inner1d
np.set_printoptions(precision=4)
np.set_printoptions(suppress=True)
np.set_printoptions(linewidth =150)
np.set_printoptions(threshold=10)
def solver(kPos, kAnchor, link0, link1, w0, cycles=1000, precision=0.001, dampening=0.1, debug=False):
"""
kPos : vector array - knot position
kAnchor : float array - knot's anchor state, 0 = moves freely, 1 = anchored (not moving)
link0 : int array - array of links connecting each knot. each index corresponds to a knot
link1 : int array - array of links connecting each knot. each index corresponds to a knot
w0 : float array - initial link length
cycles : int - eval stops when n cycles reached
precision : float - eval stops when highest applied force is below this value
dampening : float - keeps system stable during each iteration
"""
kPos = np.asarray(kPos)
pos = np.array(kPos) # copy of kPos
kAnchor = 1-np.clip(np.asarray(kAnchor).astype(float),0,1)[:,None]
link0 = np.asarray(link0).astype(int)
link1 = np.asarray(link1).astype(int)
w0 = np.asarray(w0).astype(float)
F = np.zeros(pos.shape)
i = 0
for i in xrange(cycles):
# Init force applied per knot
F = np.zeros(pos.shape)
# Calculate forces
AB = pos[link1] - pos[link0] # get link vectors between knots
w1 = np.sqrt(inner1d(AB,AB)) # get link lengths
AB/=w1[:,None] # normalize link vectors
f = (w1 - w0) # calculate force vectors
f = f[:,None] * AB
# Apply force vectors on each knot
np.add.at(F, link0, f)
np.subtract.at(F, link1, f)
# Update point positions
pos += F * dampening * kAnchor
# If the maximum force applied is below our precision criteria, exit
if np.amax(F) < precision:
break
# Debug info
if debug:
print 'Iterations: %s'%i
print 'Max Force: %s'%np.amax(F)
return pos
这里有一些测试数据来展示它是如何工作的。在这种情况下,我使用的是网格,但实际上这可以是任何类型的网络,比如带有许多结的字符串,或者是一堆多边形......:
import cProfile
# Create a 5x5 3D knot grid
z = np.linspace(-0.5, 0.5, 5)
x = np.linspace(-0.5, 0.5, 5)[::-1]
x,z = np.meshgrid(x,z)
kPos = np.array([np.array(thing) for thing in zip(x.flatten(), z.flatten())])
kPos = np.insert(kPos, 1, 0, axis=1)
'''
array([[-0.5 , 0. , 0.5 ],
[-0.25, 0. , 0.5 ],
[ 0. , 0. , 0.5 ],
...,
[ 0. , 0. , -0.5 ],
[ 0.25, 0. , -0.5 ],
[ 0.5 , 0. , -0.5 ]])
'''
# Define the links connecting each knots
link0 = [0,1,2,3,5,6,7,8,10,11,12,13,15,16,17,18,20,21,22,23,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
link1 = [1,2,3,4,6,7,8,9,11,12,13,14,16,17,18,19,21,22,23,24,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
AB = kPos[link0]-kPos[link1]
w0 = np.sqrt(inner1d(AB,AB)) # this is a square grid, each link's initial length will be 0.25
# Set the anchor states
kAnchor = np.zeros(len(kPos)) # All knots will be free floating
kAnchor[12] = 1 # Middle knot will be anchored
这是网格的样子:
如果我们使用这些数据运行我的代码,则不会发生任何事情,因为链接没有推送或拉伸:
print np.allclose(kPos,solver(kPos, kAnchor, link0, link1, w0, debug=True))
# Returns True
# Iterations: 0
# Max Force: 0.0
现在让我们将中间的锚结向上移动一点并求解系统:
# Move the center knot up a little
kPos[12] = np.array([0,0.3,0])
# eval the system
new = solver(kPos, kAnchor, link0, link1, w0, debug=True) # positions will have moved
#Iterations: 102
#Max Force: 0.000976603249133
# Rerun with cProfile to see how fast it runs
cProfile.run('solver(kPos, kAnchor, link0, link1, w0)')
# 520 function calls in 0.008 seconds
下面是网格被单个锚结拉动后的样子:
问题:
我的实际用例比这个示例稍微复杂一些,而且解决的速度对我的口味来说太慢了:(100-200 节,网络在 200-300 个链接之间,几秒钟内就可以解决)。
如何让我的求解器函数运行得更快?我会考虑 Cython,但我对 C 的经验为零。任何帮助将不胜感激。
【问题讨论】:
-
一个快速的
%lprun(IPython line_profiler 魔术)表明,大约 85% 的时间花费在内部循环中。您可以用np.add.at(F, link0, f); np.subtract.at(F, link1, f)替换该循环,以实现约 2 倍的整体加速。顺便说一句,问题具有很好的复制粘贴能力:)。 -
@morningsun :) 谢谢!我完全支持可重用性;)。它确实有帮助。我认为除此之外,诀窍是用其他东西替换主迭代循环。
标签: python algorithm numpy simulation cython