【问题标题】:store multi dimensional arrays to be operated on along all the dimensions存储要在所有维度上操作的多维数组
【发布时间】:2016-06-28 11:23:57
【问题描述】:

前言

我正在编写的 Fortran 程序应该根据ndims 处理 1D、2D 和 3D 问题,它可以是 1、2 或 3,并且是从输入文件中读取的。

在这些情况下,感兴趣的数量可以存储在数组中(可以命名为phi

  1. 等级为dimsALLOCATABLE(:)ALLOCATABLE(:,:)ALLOCATABLE(:,:,:)),
  2. 或在秩为 3 的数组中(ALLOCATABLE(:,:,:) 在 2D 中设置为等于 1 或在 1D 中第二和第三维都等于 1);

这两种情况在this answer 中都有很好的解释。第一种方法对我来说似乎更优雅,但在下面我假设第二种方法,它肯定更简单。

这些数量必须由几个子例程(例如mysub)沿ndims 维度(沿着“铅笔”应该给出图形概念)进行操作,所以我应该调用类似

SELECT CASE (ndims)

! 3D case
CASE (3)
  DO j = ...
    DO k = ...
      CALL mysub(phi(:,j,k))
    END DO
  END DO
  DO i = ...
    DO k = ...
      CALL mysub(phi(i,:,k))
    END DO
  END DO
  DO i = ...
    DO j = ...
      CALL mysub(phi(i,j,:))
    END DO
  END DO

! 2D case
CASE (2)
  DO j = ...
    DO k = ...
      CALL mysub(phi(:,j,1))
    END DO
  END DO
  DO i = ...
    DO k = ...
      CALL mysub(phi(i,:,1))
    END DO
  END DO

! 1D case
CASE (1)
  DO j = ...
    DO k = ...
      CALL mysub(phi(:,1,1))
    END DO
  END DO
END SELECT

实际问题

谁能建议我(或帮助我设计!)另一种存储phi(可能涉及派生数据类型?)的方式,以便我可以按如下方式折叠前面的代码?

DO id = 1, ndims
  CALL mysub2(phi,id)
END DO

(这里mysub2 作用于mysub 的位置。)

所以问题是我应该如何存储 phi,以便我可以用第二个代码替换第一个代码?

也许我可以回到前言并决定遵循第 1 点,在这种情况下,编写通用接口会更容易。我认为,这只是“隐藏”SELECT CASE 会做什么的一种方式。两者中的哪一个(SELECT CASE/generic INTERFACE)效率更高?

只有这两种方法可以解决这个问题吗?

【问题讨论】:

  • 这已成为一个常见问题解答。昨天最近问的——stackoverflow.com/questions/38058080/…
  • 这个问题显然是相关的,但这个问题的答案不会是这个问题的答案:如果我理解正确,这个问题是关于选择数组的等级;我的问题更多的是关于“编写代码的最佳方法,它不依赖于它所操作的数组的等级”。

标签: multidimensional-array fortran storage


【解决方案1】:

也许我误解了,但我认为具体问题的答案是根本不对 phi 的存储或声明进行任何更改。

在原始代码中,三维数据(将数据的秩与用于存储数据的数组的秩进行区分)沿第一个维度进行切片处理,然后是第二个维度,然后是第三个维度。二维数据先沿第一个处理,再沿第二个处理,一维数据仅沿第一个处理。

所以随着 id 从 1 到数据中的维数,考虑以下mysub2 的实现:

SUBROUTINE mysub2(phi, id)
  TYPE(pink_elephant), INTENT(IN) :: phi(:,:,:)
  INTEGER, INTENT(IN) :: id

  INTEGER :: i, j, k

  SELECT CASE (id)
  CASE (1)
    DO j = ...
      DO k = ...
        CALL mysub(phi(:,j,k))
      END DO
    END DO

  CASE (2)
    DO i = ...
      DO k = ...
        CALL mysub(phi(i,:,k))
      END DO
    END DO

  CASE (3)
    DO i = ...
      DO j = ...
        CALL mysub(phi(i,j,:))
      END DO
    END DO

  END SELECT
END SUBROUTINE mysub2

~~

通用接口总是可以在“编译时”解析——特定的 CALL 语句或函数引用将调用的特定过程(非类型绑定)或绑定(类型绑定)可以通过查看声明来确定在代码中。

如果您遇到“运行时”信息会影响过程选择的情况,那么除了泛型解析之外,还必须有一些其他可执行机制发挥作用 - if 语句, select case, 动态调度等等等等。

因此,询问通用解决方案是否比可执行决策更有效并不是特别有意义 - 它们是不同的东西。

【讨论】:

  • (那么,“efficient”可能不是正确的词。)在您的代码中,每次mysub2CALLed 时都会执行SELECT CASE (ndims)。如果使用SELECT CASE (ndims) 选择phi 的等级(类似于我链接的答案中所做的),则可以使用INTERFACE 块为3 个不同的等级调用3 个不同的SUBROUTINEs。这不是更快吗?
  • 通用接口只是为了将不同的过程放在同一个标​​识符后面的命名方便。除了可访问性之外,您可以简单地将通用名称更改为通用在编译时解析为的相关特定名称,并获得完全相同的行为。它们的使用不是执行效率的问题,它们的使用是“我在源代码中输入哪些字符来引用特定程序”的问题。您实际上是在问“在某个嵌套迭代结构的哪个级别做出特定决定最有效”?
  • 也许我有点困惑。你说我可以简单地将通用名称更改为相关的特定名称。这看起来很简单!我认为 SELECT CASE 会更慢。我错了吗?如果是这样,为什么? (关于引号中的最后一个问题,我没有明白。)
  • 另一个问题,以更好地理解您的答案。您说通用接口总是可以在“编译时”解决。如果需要运行时信息(就像文件读取的情况一样,不是吗?),界面是不够的。所以你的意思是在这种情况下接口在编译时没有解析?
  • 如果您根据仅在运行时可用的信息做出决定,则必须在运行时通过ifselect case 或其他方式做出决定,在某个阶段。您何时以及如何做出运行时决定将影响性能。但是泛型接口的解析不是运行时决定——泛型解析到的特定过程或绑定是根据引用中实际参数的声明类型、种类和等级来选择的泛型以及实际参数的声明类型、种类和等级在编译时始终是已知的。
【解决方案2】:

你可能想要这样的东西:

program test

   integer :: j,ndims
   integer :: n ! rank of each dimension, could also be read from input an allocated separately

   type arr
      real(8) :: x(n) ! one array for each dimension
   end type

   type(arr),allocatable :: phi

   read(*,*) ndims
   allocate(phi(ndims))

   do j=1,ndims
      call mysub(phi(j)%x) ! acts on the array in dimension j
   end do

contains

   subroutine mysub(x)
   ...
   end subroutine

end program

【讨论】:

  • 好吧,这可以作为一个起点。我希望你能提供进一步的帮助。您的回答有效地展示了如何在维度数量 ndims 上执行 DO 循环。另一方面,这样做,arr 将不会包含 phi 将包含的相同信息。
  • 我在实际问题中添加了一些细节。
  • 是的,我想我回答得有点快^^。
  • 我的意思是最简单的答案是将你的第一个代码打包到一个子程序mysub2(phi,id) 然后你可以在你的主代码中使用mysub2。但这可能没有你想要的那么优雅?
猜你喜欢
  • 1970-01-01
  • 2017-05-18
  • 1970-01-01
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
  • 2018-08-06
相关资源
最近更新 更多