Изменение размеров массива в fortran

Существуют два способа передачи массивов в подпрограмму в 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

Я задавался вопросом, есть ли хороший способ делать такие вещи, используя второй, предполагаемый интерфейс формы, не копируя его.

Ответ 1

См. внутреннее значение RESHAPE, например

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

В качестве альтернативы, если вы хотите избежать копирования (в некоторых случаях оптимизационный компилятор может выполнить преобразование без копирования, например, если массив RHS не будет использоваться впоследствии, но я не буду рассчитывать на него), поскольку из Fortran 2003 вы можете назначать указатели для целей разного ранга, используя переопределение границ. Например. что-то вроде


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

Ответ 2

@janneb уже ответил re RESHAPE. RESHAPE - это функция, обычно используемая в инструкции присваивания, поэтому будет операция копирования. Возможно, это можно сделать без копирования с помощью указателей. Если массив не огромен, вероятно, лучше использовать RESHAPE.

Я скептически отношусь к тому, что явный массив формы более эффективен, чем предполагаемая форма, с точки зрения времени выполнения. Моя склонность состоит в том, чтобы использовать функции языка Fortran >= 90 и использовать принятые формы объявлений... таким образом, вам не нужно беспокоиться о передаче измерений.

EDIT: Я проверил образец программы @janneb с ifort 11, gfortran 4.5 и gfortran 4.6. Из этих трех он работает только в gfortran 4.6. Интересно, что для перехода в другом направлении и подключения массива с двумя массивами к существующему 2-мерному массиву требуется еще одна новая функция 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

Ответ 3

Я хотел сделать то же самое и наткнулся на эту дискуссию. Ни одно из решений не соответствовало моим целям, но я обнаружил, что существует способ изменить массив без копирования данных с помощью iso_c_binding, если вы используете стандарт fortran 2003, который, как правило, поддерживает текущий компилятор fortran 90/95. Я знаю, что дискуссия устарела, но я решил, что добавлю то, что я придумал, для других с этим вопросом.

Ключ должен использовать функцию C_LOC для преобразования массива в указатель массива, а затем использовать C_F_POINTER, чтобы преобразовать это обратно в указатель массива fortran с нужной формой. Одна из проблем с использованием C_LOC заключается в том, что C_LOC работает только для массива с прямой спецификацией. Это связано с тем, что массивы в fortran с неполной спецификацией размера (то есть, которые используют a: для некоторой размерности) включают дескриптор массива вместе с данными массива. 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

Ответ 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, чтобы гарантировать согласованность преобразования.

Ответ 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" решает проблему четыре "маршрута1", но не для "маршрута2".

Однако вы можете обмануть gfortran и сказать измерение (0: 10 + 0 * sz) вместо измерения (0:10) и 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: "Я еще не знаю размерности, но мы это исправим". Но для этого нужен запасной аргумент integer (или что-то еще, что мы можем превратить в целое число), чтобы обмануть gfortran.