web-dev-qa-db-ja.com

fortranでの配列の次元の変更

Fortran 90/95のサブルーチンに配列を渡すには、基本的に2つの方法があります。

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

通常はモジュールを使用して、2番目の明示的なインターフェイスが必要な場合。

FORTRAN77から、私は最初の選択肢に慣れています。配列全体を渡す場合、これも最も効率的であると読みました。

明示的な形状の良い点は、サブルーチンを呼び出して、配列を行列ではなくベクトルとして扱うこともできることです。

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

2番目の想定された形状インターフェースをコピーせずに使用してそのようなことを行う良い方法があるかどうか疑問に思いました。

14
steabert

RESHAPE組み込みを参照してください。

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

あるいは、Fortran 2003の時点で、コピーを回避したい場合(RHS配列が後で使用されない場合など、最適化コンパイラーがコピーせずに形状変更を実行できる場合があります)。 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
8
janneb

私は同じことをしようとしていて、この議論に出くわしました。私の目的に合ったソリューションはありませんでしたが、現在のFortran90/95コンパイラがサポートする傾向があるFortran2003標準を使用している場合は、iso_c_bindingを使用してデータをコピーせずに配列を再形成する方法があることがわかりました。議論が古いことは知っていますが、この質問で他の人の利益のために私が思いついたものを追加すると思いました。

重要なのは、関数C_LOCを使用して配列を配列ポインターに変換してから、C_F_POINTERを使用してこれを目的の形状のFortran配列ポインターに変換し直すことです。 C_LOCを使用する際の1つの課題は、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
8
Lance Larsen

@jannebはすでにRESHAPEについて回答しています。 RESHAPEは関数です。通常、代入ステートメントで使用されるため、コピー操作が行われます。おそらく、ポインタを使用してコピーせずに実行できます。配列が巨大でない限り、RESHAPEを使用する方がおそらく良いでしょう。

実行時間の観点から、明示的な形状配列が想定された形状よりも効率的であることに懐疑的です。私の傾向は、Fortran> = 90言語の機能を使用し、想定される形状宣言を使用することです...そうすれば、わざわざ次元を渡す必要がなくなります。

編集:@jannebのサンプルプログラムをifort 11、gfortran 4.5、gfortran4.6でテストしました。これら3つのうち、gfortran4.6でのみ機能します。興味深いことに、反対方向に進み、1-D配列を既存の2-D配列に接続するには、Fortran 2008の別の新機能である「連続」属性が必要です。少なくともgfortran 4.6.020110318によると。宣言、コンパイル時エラーがあります。

    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
6
M. S. B.

想定サイズの配列を使用できますが、ラッパールーチンの複数のレイヤーを意味する場合があります。

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
1
Michael Goerz

Gfortranは、インターフェイスに関して少し偏執的です。引数のタイプ、種類、ランク、数だけでなく、形状、ターゲット属性、およびインテントも知りたいのです(インテントの部分には同意しますが)。同様の問題が発生しました。

Gfortranには、3つの異なるディメンション定義があります。
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」に変更すると、問題4の「rout1」は解決されますが、「rout2」は解決されません。

ただし、gfortranをだまして、dimension(0:10)の代わりにdimension(0:10 + 0 * sz)と言うと、gfortranがコンパイルされ、ifortと同じ結果が得られます。

これはばかげたトリックであり、そこにない可能性のある整数「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をだますために、予備の整数引数(または整数に変換できる何か)が必要です。

0

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の連続属性を使用する必要がありました。

0
Amir