在不复制大量代码的情况下访问数组的边界

时间:2018-11-18 21:42:22

标签: arrays fortran

当我尝试使用-fcheck=all编译代码时,出现运行时错误,因为似乎我超出了数组维数的范围。它来自下面显示的我的代码部分。我认为这是因为我在i,j上的循环仅从-nyny,从-nxnx,但是我尝试使用i+1,j+1,i-1,j-1上的点带我走出了数组的界限。当j上的循环从-ny开始时,它需要j-1,所以由于我要访问-ny-1,因此它立即使我越界。 j=ny, i=-nx,nx时类似。

我的问题是,如何使用最少的代码有效地解决此问题?

我需要在边界上正确定义array grad(1,i,j),并且需要完全按照下面等式的右侧进行定义,我只是不知道执行此操作的有效方法。我可以分别明确定义grad(1,nx,j), grad(1,-nx,j), etc,,并且仅循环遍历i=-nx+1,nx-1,j=-ny+1,ny-1,但这会导致很多重复的代码,而且我有很多这样的数组,因此我认为这不是逻辑/有效的方法。如果这样做,我将得到数百行重复的代码,这使得调试非常困难。谢谢。

integer :: i,j
integer, parameter :: nx = 50, ny = 50
complex, dimension (3,-nx:nx,-ny:ny) :: grad,psi
real, parameter :: h = 0.1

do j = -ny,ny
do i = -nx,nx

    psi(1,i,j) = sin(i*h)+sin(j*h)
    psi(2,i,j) = sin(i*h)+sin(j*h)    
    psi(3,i,j) = sin(i*h)+sin(j*h)

end do
end do

do j = -ny,ny
do i = -nx,nx

    grad(1,i,j) = (psi(1,i+1,j)+psi(1,i-1,j)+psi(1,i,j+1)+psi(1,i,j-1)-4*psi(1,i,j))/h**2 & 

                - (psi(2,i+1,j)-psi(2,i,j))*psi(1,i,j)/h & 

                - (psi(3,i,j+1)-psi(3,i,j))*psi(1,i,j)/h &

                - psi(2,i,j)*(psi(1,i+1,j)-psi(1,i,j))/h &

                - psi(3,i,j)*(psi(1,i,j+1)-psi(1,i,j))/h

end do
end do

如果我直接为grad(1,nx,j), grad(1,-nx,j)执行此操作,则将由

给出
   do j = -ny+1,ny-1

       grad(1,nx,j) = (psi(1,nx,j)+psi(1,nx-2,j)+psi(1,nx,j+1)+psi(1,nx,j-1)-2*psi(1,nx-1,j)-2*psi(1,nx,j))/h**2 & 

                - (psi(2,nx,j)-psi(2,nx-1,j))*psi(1,nx,j)/h & 

                - (psi(3,nx,j+1)-psi(3,nx,j))*psi(1,nx,j)/h &

                - psi(2,nx,j)*(psi(1,nx,j)-psi(1,nx-1,j))/h &

                - psi(3,nx,j)*(psi(1,nx,j+1)-psi(1,nx,j))/h

       grad(1,-nx,j) = (psi(1,-nx+2,j)+psi(1,-nx,j)+psi(1,-nx,j+1)+psi(1,-nx,j-1)-2*psi(1,-nx+1,j)-2*psi(1,-nx,j))/h**2 & 

                - (psi(2,-nx+1,j)-psi(2,-nx,j))*psi(1,-nx,j)/h & 

                - (psi(3,-nx,j+1)-psi(3,-nx,j))*psi(1,-nx,j)/h &

                - psi(2,-nx,j)*(psi(1,-nx+1,j)-psi(1,-nx,j))/h &

                - psi(3,-nx,j)*(psi(1,-nx,j+1)-psi(1,-nx,j))/h

   end do

2 个答案:

答案 0 :(得分:2)

一种可能的方法是对边界使用附加的索引变量,该变量是从原始索引修改而来的,以避免越界。我的意思是这样的:

do j = -ny,ny
  jj = max(min(j, ny-1), -ny+1)
  do i = -nx,nx
    ii = max(min(i, nx-1), -nx+1)
    grad(1,i,j) = (psi(1,ii+1,j)+psi(1,ii-1,j)+psi(1,i,jj+1)+psi(1,i,jj-1)-4*psi(1,i,j))/h**2 &
                - (psi(2,ii+1,j)-psi(2,ii,j))*psi(1,i,j)/h &
                - (psi(3,i,jj+1)-psi(3,i,jj))*psi(1,i,j)/h &
                - psi(2,i,j)*(psi(1,ii+1,j)-psi(1,ii,j))/h &
                - psi(3,i,j)*(psi(1,i,jj+1)-psi(1,i,jj))/h
  end do
end do

我很难编写适当的代码,因为似乎您在问题中提出的代码中修剪了原始表达式的一部分,但我希望您理解该想法并将其正确地应用于您的逻辑。

意见

  1. 尽管这是您要的(据我了解),但我不建议您在进行概要分析之前检查并检查整个数组操作后手动分配边界条件是否会更有效。也许在每次迭代中对索引进行的那些额外计算可能会影响性能(可以说少于if条件或函数调用)。如@evets所建议的那样,使用“幽灵细胞”可能会更有效。您应该进行概要分析和比较。
  2. 我建议您改为将数组声明为dimension(-nx:nx,-ny:ny,3)。 Fortran以列优先顺序存储数组,并且当您访问“ x”和“ y”附近的值时,它们将是不连续的存储位置,因为固定的“ other”维是最左侧的,因此意味着更少的缓存命中。

答案 1 :(得分:1)

可以用伪代码完成

do j = -ny, ny

   if (j == -ny) then
      p1jm1 = XXXXX    ! Some boundary condition
   else
      p1jm1 = psi(1,i,j-1)
   end if

   if (j == ny) then
      p1jp1 = YYYYY   ! Some other boundary condition
   else
      p1jp1 = psi(1,i,j+1)
   end if

   do i = -nx, ny
      grad(1,i,j) = ... term involving p1jm1 ...  term involving p1jp1 ...
      ...
   end do
end do

j循环还不错,因为您要添加2 * 2 * ny条件。内部i循环为每个j迭代添加2 * 2 * nx个条件(或2 * 2 * ny * 2 * 2 * nx个条件)。请注意,您需要为每个psi临时设置一个三元组索引,这些索引是唯一的,即psi(1,i,j+1)psi(1,i,j-1)psi(3,i,j+1)