我有一个代码,我想循环遍历网格中的所有点,并为每个点检查给定条件是否适用于足够数量的相邻点。另外,我在网格上有周期性边界。
问题与生命游戏非常相似。
我当前的代码看起来像这样
do k=1,ksize; do j=1,jsize; do i=1,isize ! Loop over all points
ncount = 0
kkloop: do kk=k-1,k+1 ! Loop over neighbours
ktmp = kk
if(kk>ksize) ktmp = 1 ! Handle periodic boundary
if(kk<1) ktmp = ksize
do jj=j-1,j+1
jtmp = jj
if(jj>jsize) jtmp = 1
if(jj<1) jtmp = jsize
do ii=i-1,i+1
if(ii == 0 .and. jj == 0 .and. kk == 0) cycle ! Skip self
itmp = ii
if(ii>isize) itmp = 1
if(ii<1) itmp = isize
if(grid(itmp,jtmp,ktmp)) ncount = ncount + 1 ! Check condition for neighbour
if(ncount > threshold) then ! Enough neigbours with condition?
do_stuff(i,j,k)
break kkloop
end if
end do
end do
end do
end do; end do; end do
这既不优雅,也不高效。有一个更好的方法吗?这段代码会重复很多,所以我想尽可能快地完成。
答案 0 :(得分:5)
我会在2D中解决这个问题,留给你充气到3D。
我要做的第一件事是用一个深度等于你感兴趣的邻域深度的光晕填充数组。所以,如果你的数组被声明为,请说
real, dimension(100,100) :: my_array
你对每个细胞的8个直接邻居感兴趣,
real, dimension(0:101,0:101) :: halo_array
.
.
.
halo_array(1:100,1:100) = my_array
halo_array(0,:) = my_array(100,:)
! repeat for each border, mutatis mutandis
这将节省大量时间检查边界,无论您是否遵循下一个建议,都值得做。如果你愿意,你可以“就地”这样做,我的意思是只展开my_array
而不是复制它。
对于一个优雅的解决方案,你可以写这样的东西
forall (i=1:100,j=1:100)
if (logical_function_of(my_array(i-1,j),my_array(i+1,j),my_array(i,j-1),my_array(i,j+1),...) then
do_stuff(my_array(i,j))
end if
end forall
此处,当logical_function_of()
的邻域满足您的条件时,my_array(i,j)
会返回true。列出N,S,E,W邻居后我感到很累,对于生产代码,我可能会将其作为索引的函数编写。根据我的经验,forall
优雅(对某些人而言)但不如嵌套循环那么高。
答案 1 :(得分:1)
您可以使用kd-tree或八叉树来细分3d数组。像z莫顿次序曲线那样的空间填充曲线在3d中用于为八个立方体创建一个键。但这最适合2 3d阵列的功能。
答案 2 :(得分:1)
您正在为网格上的每个点调用grid
函数27次。如果这是一个昂贵的电话,你想少打电话。
例如,如果grid
返回真值的概率很低,您可以为网格上的每个点调用它,并将条件所在的那些点存储在kd树中。然后,更容易迭代kd树内的点,计算每个点的邻居。
否则,您可以使用维度isize*jsize*3
的位矩阵来缓存kk=k-1,k+1
的所有点的网格值。如果这个太大,您可以使用大小为isize*3*3
的位矩阵进行缓存的中间解决方案。