Изменение размеров массива в 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.