需要将Python编写的好友推荐算法转换为Fortran 90/95
我正在尝试自己写一个“朋友的朋友”算法。这种算法处理一组三维数据点,并返回数据集中“光环”的数量。每个光环是指那些距离小于链接长度b的点的集合,而链接长度是这个程序唯一的参数。
算法描述: FOF算法有一个自由参数,叫做链接长度。任何两个粒子,如果它们之间的距离小于或等于链接长度,就被称为“朋友”。FOF组是由一组粒子组成的,这组粒子中的每一个都通过朋友网络与组内的其他粒子相连。
设置FOF组计数器j=1。
对于每个尚未与任何组关联的粒子n:
将n分配到组j,为组j初始化一个新的成员列表mlist,粒子n作为第一个条目,
递归地,对于mlist中的每个新粒子p:
找到距离p小于或等于链接长度的邻居,将那些尚未分配到组j的粒子添加到mlist中,
记录组j的mlist,设置j=j+1。
这是我尝试编写算法的代码。我最熟悉的语言是Python。不过,我需要将这段代码用Fortran写,或者让它运行得更快。我真的希望有人能帮我。
首先,我生成了一组点,应该模拟出3个光环的存在:
import random
from random import *
import math
from math import *
import numpy
from numpy import *
import time
points = 1000
halos=[0,100.,150.]
x=[]
y=[]
z=[]
id=[]
for i in arange(0,points,1):
x.append(halos[0]+random())
y.append(halos[0]+random())
z.append(halos[0]+random())
id.append(i)
for i in arange(points,points*2,1):
x.append(halos[1]+random())
y.append(halos[1]+random())
z.append(halos[1]+random())
id.append(i)
for i in arange(points*2,points*3,1):
x.append(halos[2]+random())
y.append(halos[2]+random())
z.append(halos[2]+random())
id.append(i)
然后我编写了FOF算法:
x=array(x)
y=array(y)
z=array(z)
id=array(id)
t0 = time.time()
id_grp=[]
groups=zeros((len(x),1)).tolist()
particles=id
b=1 # linking length
while len(particles)>0:
index = particles[0]
# remove the particle from the particles list
particles.remove(index)
groups[index]=[index]
print "#N ", index
dx=x-x[index]
dy=y-y[index]
dz=z-z[index]
dr=sqrt(dx**2.+dy**2.+dz**2.)
id_to_look = where(dr<b)[0].tolist()
id_to_look.remove(index)
nlist = id_to_look
# remove all the neighbors from the particles list
for i in nlist:
if (i in particles):
particles.remove(i)
print "--> neighbors", nlist
groups[index]=groups[index]+nlist
new_nlist = nlist
while len(new_nlist)>0:
index_n = new_nlist[0]
new_nlist.remove(index_n)
print "----> neigh", index_n
dx=x-x[index_n]
dy=y-y[index_n]
dz=z-z[index_n]
dr=sqrt(dx**2.+dy**2.+dz**2.)
id_to_look = where(dr<b)[0].tolist()
id_to_look = list(set(id_to_look) & set(particles))
nlist = id_to_look
if (len(nlist)==0):
print "No new neighbors found"
else:
groups[index]=groups[index]+nlist
new_nlist=new_nlist+nlist
print "------> neigh-neigh", new_nlist
for k in nlist:
particles.remove(k)
最后,我们会得到一个包含光环的列表,存储在groups
中。
这部分代码有点偏题,但我觉得展示给你们看也不错。我基本上是在删除所有没有粒子的组,按照粒子的数量进行排序,并展示一些属性。
def select(test,list):
selected = []
for item in list:
if test(item) == True:
selected.append(item)
return selected
groups=select(lambda x: sum(x)>0.,groups)
# sorting groups
groups.sort(lambda x,y: cmp(len(x),len(y)))
groups.reverse()
print time.time() - t0, "seconds"
mass=x
for i in arange(0,len(groups),1):
total_mass=sum([mass[j] for j in groups[i]])
x_cm = sum([mass[j]*x[j] for j in groups[i]])/total_mass
y_cm = sum([mass[j]*y[j] for j in groups[i]])/total_mass
z_cm = sum([mass[j]*z[j] for j in groups[i]])/total_mass
dummy_x_cm = [x[j]-x_cm for j in groups[i]]
dummy_y_cm = [y[j]-y_cm for j in groups[i]]
dummy_z_cm = [z[j]-z_cm for j in groups[i]]
dummy_x_cm = array(dummy_x_cm)
dummy_y_cm = array(dummy_y_cm)
dummy_z_cm = array(dummy_z_cm)
dr = max(sqrt(dummy_x_cm**2.+dummy_y_cm**2.+dummy_z_cm**2.))
dummy_x_cm = max(dummy_x_cm)
dummy_y_cm = max(dummy_y_cm)
dummy_z_cm = max(dummy_z_cm)
print i, len(groups[i]), x_cm, y_cm, z_cm,dummy_x_cm,dummy_y_cm,dummy_z_cm
3 个回答
如果你有一块现代的显卡,可以在Python代码中利用 PyOpenCL 实现数百个处理器的并行计算(具体数量取决于你的显卡)。
你可以去看看这个 voidfinder F90 代码 中是否实现了FoF算法。
你可以把距离定义为平方距离,这样就不需要用到sqrt()函数了,可以用x*x代替x**2...
这是一个关于更高效算法的建议。如果我没记错的话,你是在把一个点和其他每个点进行比较,看看有没有比链接长度更近的点。对于大量的点,有更快的方法可以找到最近的邻居,比如空间索引和KD树,这些都是我能想到的,但肯定还有其他方法也能帮到你。
我觉得如果你希望通过学习Fortran来让你的代码比现在的实现更快,那你可能会走错路。虽然最终可能会更快,但我建议你先把你的Python代码优化到最快,再考虑用其他语言来实现,尤其是像Fortran这样不太常用的语言。
我写Fortran,个人觉得它的性能比Python强多了。不过,有些懂行的人认为,如果你认真写,Python加上SciPy和Numpy可以在很多科学和工程程序的计算核心部分与Fortran的速度相匹配。别忘了,直到你把电脑的所有核心都用到极限,你的Python代码才算真正优化过。
所以:
第一步 - 先在Python中实现一个可用的版本。
第二步 - 尽量把这个实现做得快一些。
如果(这里的“如果”很重要)代码还是不够快,而且把它转换成编译语言的成本和收益比较合适,那么再考虑用哪种编译语言来转换。如果你所在的领域广泛使用Fortran,那就学Fortran吧,但它算是小众语言,学C++或者类似的语言可能对你的职业发展更有帮助。
补充说明(太长,不能放在评论框里)
为什么在你的问题中误导我们呢?你说你只会Python,现在又说你会Fortran。我想你可能对Fortran不太熟悉。而且,从你的评论来看,似乎你真正需要的帮助是让你的Python实现更快;Sideshow Bob给出了一些建议。考虑一下这些建议,然后再进行并行处理。