【问题标题】:changing array dimensions in fortran在 fortran 中更改数组维度
【发布时间】:2011-07-21 07:58:09
【问题描述】:

在 Fortran 90/95 中,基本上有两种方法可以将数组传递给子例程:

PROGRAM ARRAY
INTEGER, ALLOCATABLE :: A(:,:)
INTEGER :: N
ALLOCATE(A(N,N))
CALL ARRAY_EXPLICIT(A,N)
! or
CALL ARRAY_ASSUMED(A)
END PROGRAM ARRAY

SUBROUTINE ARRAY_EXPLICIT(A,N)
INTEGER :: N
INTEGER :: A(N,N)
! bla bla
END SUBROUTINE ARRAY_EXPLICIT

SUBROUTINE ARRAY_ASSUMED(A)
INTEGER, ALLOCATABLE :: A(:,:)
N=SIZE(A,1)
! bla bla
END SUBROUTINE ARRAY_ASSUMED

第二个需要显式接口,通常通过使用模块。

从 FORTRAN77 开始,我习惯了第一种选择,我读到如果你传递整个数组,这也是最有效的。

显式形状的好处是我还可以调用子例程并将数组视为向量而不是矩阵:

SUBROUTINE ARRAY_EXPLICIT(A,N)
INTEGER :: N
INTEGER :: A(N**2)
! bla bla
END SUBROUTINE ARRAY_EXPLICIT

我想知道是否有一种很好的方法可以使用第二个假定形状的界面来完成这种事情,而无需复制它。

【问题讨论】:

  • "不复制它。"不复制什么?
  • 我认为@steabert 的意思是原地改变数组的形状,而不是将其复制到一维数组中。
  • 谢谢@M。 S. B. 澄清一下,这就是我的意思。所以不是下面提到的reshape 内在解决方案
  • @ M. S. B. 我怀疑。比重塑内在不是解决方案。好吧,我认为事情很大程度上取决于故事的“!bla,bla”部分。 =) 你总是可以使用循环遍历你的二维数组,因为它是一维的。
  • @kemiisto 是的,您始终可以控制索引事物的方式,但我想知道这种“手动”处理是否是唯一的方法。比如说,我有一个接受 NxN 矩阵的子程序,我想传递的矩阵可以通过在 MxMxMxM 形状上使用 4 个索引循环最容易地填充,然后我可以优雅地使用显式形状虚拟对象。

标签: fortran


【解决方案1】:

查看 RESHAPE 内在函数,例如

http://gcc.gnu.org/onlinedocs/gfortran/RESHAPE.html

或者,如果您想避免复制(在某些情况下,优化编译器可能能够在不复制的情况下进行重塑,例如,如果 RHS 数组之后不使用,但我不会指望它),如在 Fortran 2003 中,您可以使用 bounds remapping 将指针分配给不同等级的目标。例如。像

program ptrtest
  real, pointer :: a(:)
  real, pointer :: b(:,:)
  integer :: n = 10
  allocate(a(n**2))
  a = 42
  b (1:n, 1:n) => a
end program ptrtest

【讨论】:

  • +1,但是,你的例子在我的情况下不起作用,我用 ifort 11 编译。b 的报告大小是 2*n**2,是 a 的两倍。跨度>
  • 只有最新的编译器支持 Fortran 2003 的这个特性,称为指针边界重映射。我认为 gfortran 4.6 但不是 4.5,英特尔 12 但不是 11.1。
  • @M. S.B.谢谢!顺便说一句,现在你说,它确实是 ifort 12,而不是 11。我仍然没有让它工作,很奇怪。有人可以用 ifort 12 检查吗?
  • 我刚刚在 Mac OSX 10.11.6 (ifort 17.0.7 20180403) 上使用 Intel 的 fortran 编译器测试了上面的 ptrtest 程序。我编译了一次 with 和一次 without 以下标志:debug all -g -traceback -gen-interfaces -warn interfaces -check bounds -check arg_temp_created -check uninit -check all,noarg_temp_created。在这两种情况下,它都会编译并运行而不会出现警告或错误。
【解决方案2】:

我想做同样的事情并遇到了这个讨论。没有一个解决方案适合我的目的,但我发现如果您使用当前 fortran 90/95 编译器倾向于支持的 fortran 2003 标准,则有一种方法可以在不使用 iso_c_binding 复制数据的情况下重塑数组。我知道讨论已经过时了,但我想我会在这个问题上添加我想出的内容,以帮助其他人。

关键是使用函数 C_LOC 将数组转换为数组指针,然后使用 C_F_POINTER 将其转换回具有所需形状的 fortran 数组指针。使用 C_LOC 的一个挑战是 C_LOC 仅适用于具有直接指定形状的数组。这是因为 fortran 中具有不完整大小规范的数组(即,对某些维度使用 :)包含数组描述符以及数组数据。 C_LOC 没有给你数组数据的内存位置,而是描述符的位置。因此,可分配数组或指针数组不适用于 C_LOC(除非您想要编译器特定数组描述符数据结构的位置)。解决方案是创建一个子例程或函数,将数组作为固定大小的数组接收(大小真的无关紧要)。这会导致函数(或子例程)中的数组变量指向数组数据的位置,而不是数组描述符的位置。然后,您使用 C_LOC 获取指向数组数据位置的指针,并使用 C_F_POINTER 将此指针转换回具有所需形状的数组。必须将所需的形状传递给此函数才能与 C_F_POINTER 一起使用。下面是一个例子:

program arrayresize
  implicit none
  integer, allocatable :: array1(:)
  integer, pointer :: array2(:,:)

  ! allocate and initialize array1
  allocate(array1(6))
  array1 = (/1,2,3,4,5,6/)

  ! This starts out initialized to 2
  print *, 'array1(2) = ', array1(2)

  ! Point array2 to same data as array1. The shape of array2
  ! is passed in as an array of intergers because C_F_POINTER
  ! uses and array of intergers as a SIZE parameter.
  array2 => getArray(array1, (/2,3/))

  ! Change the value at array2(2,1) (same as array1(2))
  array2(2,1) = 5

  ! Show that data in array1(2) was modified by changing
  ! array2(2,1)
  print *, 'array(2,1) = array1(2) = ', array1(2)

contains

  function getArray(array, shape_) result(aptr)
    use iso_c_binding, only: C_LOC, C_F_POINTER
    ! Pass in the array as an array of fixed size so that there
    ! is no array descriptor associated with it. This means we
    ! can get a pointer to the location of the data using C_LOC
    integer, target :: array(1)
    integer :: shape_(:)
    integer, pointer :: aptr(:,:)

    ! Use C_LOC to get the start location of the array data, and
    ! use C_F_POINTER to turn this into a fortran pointer (aptr).
    ! Note that we need to specify the shape of the pointer using an
    ! integer array.
    call C_F_POINTER(C_LOC(array), aptr, shape_)
  end function
end program

【讨论】:

  • 这类似于 janneb 的回答:“从 Fortran 2003 开始​​,您可以将指针分配给不同等级的目标”,它还依赖于 F2003 功能,但只会更复杂......
【解决方案3】:

@janneb 已经回答了重新重塑。 RESHAPE 是一个函数——通常在赋值语句中使用,因此会有一个复制操作。也许它可以在不使用指针复制的情况下完成。除非数组很大,否则最好使用 RESHAPE。

我怀疑显式形状数组在运行时比假设形状更有效。我倾向于使用 Fortran >=90 语言的特性并使用假定的形状声明……这样您就不必费心传递尺寸了。

编辑: 我用 ifort 11、gfortran 4.5 和 gfortran 4.6 测试了 @janneb 的示例程序。在这三个中,它仅适用于 gfortran 4.6。有趣的是,要走另一个方向并将一维数组连接到现有的二维数组,需要 Fortran 2008 的另一个新特性,即“连续”属性——至少根据 gfortran 4.6.0 20110318。没有这个属性声明,编译时出错。

    program test_ptrs

   implicit none

   integer :: i, j

   real, dimension (:,:), pointer, contiguous :: array_twod
   real, dimension (:), pointer :: array_oned

   allocate ( array_twod (2,2) )

   do i=1,2
      do j=1,2
         array_twod (i,j) = i*j
      end do
   end do

   array_oned (1:4) => array_twod

   write (*, *) array_oned

   stop

end program test_ptrs

【讨论】:

  • 是的,我知道 reshape 函数,但是就像你说的,这对于巨大的数组来说是有问题的,例如如果其中 2 个不适合内存。事实上,显式形状更有效,我在第 40-41 页的software.intel.com/file/6397 上读到了这一点。您对使用 Fortran>=90 的新功能是正确的,这就是为什么我想为假人使用假定形状并使用显式接口。这就是为什么我想知道重塑,如果它可能就地。
【解决方案4】:

您可以使用假定大小的数组,但这可能意味着多层包装 套路:

program test

  implicit none

  integer :: test_array(10,2)

  test_array(:,1) = (/1,   2,  3,  4,  5,  6,  7,  8,  9, 10/)
  test_array(:,2) = (/11, 12, 13, 14, 15, 16, 17, 18, 19, 20/)

  write(*,*) "Original array:"
  call print_a(test_array)

  write(*,*) "Reshaped array:"
  call print_reshaped(test_array, size(test_array))

contains

  subroutine print_reshaped(a, n)
  integer, intent(in) :: a(*)
  integer, intent(in) :: n
  call print_two_dim(a, 2, n/2)
  end subroutine

  subroutine print_two_dim(a, n1, n2)
  integer, intent(in) :: a(1:n1,1:*)
  integer, intent(in) :: n1, n2
  call print_a(a(1:n1,1:n2))
  end subroutine

  subroutine print_a(a)
  integer, intent(in) :: a(:,:)
  integer :: i
  write(*,*) "shape:", shape(a)
  do i = 1, size(a(1,:))
      write(*,*) a(:,i)
  end do
  end subroutine

end program test

【讨论】:

    【解决方案5】:

    我正在使用 ifort 14.0.3 和 2D 到 1D 的转换,我可以为 2D 数组使用可分配数组,为 1D 使用指针数组:

    integer,allocatable,target :: A(:,:)
    integer,pointer :: AP(:)
    
    allocate(A(3,N))
    AP(1:3*N) => A
    

    正如@M.S.B所说,如果A和AP都有指针属性,我不得不为A使用contiguous属性来保证转换的一致性。

    【讨论】:

      【解决方案6】:

      Gfortran 对接口有点偏执。它不仅想知道参数的类型、种类、等级和数量,还想知道形状、目标属性和意图(尽管我同意意图部分)。我遇到了类似的问题。

      使用gfortran,有三种不同的维度定义:
      1. 固定
      2. 变量
      3. 假定尺寸

      使用 ifort,类别 1 和类别 2 被认为是相同的,因此您只需在界面中将任何尺寸大小定义为 0 即可。

      program test
      
        implicit none
      
        integer, dimension(:), allocatable :: ownlist
      
        interface
          subroutine blueprint(sz,arr)
            integer, intent(in) :: sz
            integer, dimension(0), intent(in) :: arr
            ! This zero means that the size does not matter,
            ! as long as it is a one-dimensional integer array.
          end subroutine blueprint
        end interface
      
        procedure(blueprint), pointer :: ptr
      
        allocate(ownlist(3))
        ownlist = (/3,4,5/)
        ptr => rout1
        call ptr(3,ownlist)
        deallocate(ownlist)
      
        allocate(ownlist(0:10))
        ownlist = (/3,4,5,6,7,8,9,0,1,2,3/)
        ptr => rout2
        call ptr(3,ownlist)
        deallocate(ownlist)
      
      contains
      
        ! This one has a dimension size as input.
        subroutine rout1(sz,arr)
          implicit none
          integer, intent(in) :: sz
          integer, dimension(sz), intent(in) :: arr
          write(*,*) arr
          write(*,*) arr(1)
        end subroutine rout1
      
        ! This one has a fixed dimension size.
        subroutine rout2(sz,arr)
          implicit none
          integer, intent(in) :: sz
          integer, dimension(0:10), intent(in) :: arr
          write(*,*) "Ignored integer: ",sz
          write(*,*) arr
          write(*,*) arr(1)
        end subroutine rout2
      
      end program test
      

      Gfortran 抱怨界面。将 0 改成 'sz' 解决了问题四 'rout1',但不能解决 'rout2' 的问题。

      但是,您可以欺骗 gfortran 并说 dimension(0:10+0*sz) 而不是 dimension(0:10) 并且 gfortran 编译并给出相同的 结果如愿。

      这是一个愚蠢的技巧,它依赖于可能不存在的整数“sz”的存在。另一个程序:

      program difficult_test
      
        implicit none
      
        integer, dimension(:), allocatable :: ownlist
      
        interface
          subroutine blueprint(arr)
            integer, dimension(0), intent(in) :: arr
          end subroutine blueprint
        end interface
      
        procedure(blueprint), pointer :: ptr
      
        allocate(ownlist(3))
        ownlist = (/3,4,5/)
        ptr => rout1
        call ptr(ownlist)
        deallocate(ownlist)
      
        allocate(ownlist(0:10))
        ownlist = (/3,4,5,6,7,8,9,0,1,2,3/)
        ptr => rout2
        call ptr(ownlist)
        deallocate(ownlist)
      
      contains
      
        subroutine rout1(arr)
          implicit none
          integer, dimension(3), intent(in) :: arr
          write(*,*) arr
          write(*,*) arr(1)
        end subroutine rout1
      
        subroutine rout2(arr)
          implicit none
          integer, dimension(0:10), intent(in) :: arr
          write(*,*) arr
          write(*,*) arr(1)
        end subroutine rout2
      
      end program difficult_test
      

      这可以在 ifort 下工作,原因与前面的示例相同,但 gfortran 抱怨该接口。我不知道如何修复它。

      我想告诉 gfortran 的唯一一件事是“我还不知道尺寸大小,但我们会解决它。”。但这需要一个多余的整数参数(或其他我们可以变成整数的东西)来欺骗 gfortran。

      【讨论】:

      • 它很长,但我不确定你是否在回答 OP 实际提出的问题。
      猜你喜欢
      • 2023-03-13
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-12
      • 2011-09-24
      相关资源
      最近更新 更多