Указатели подпрограмм Fortran для несоответствия размеров массива

У меня проблема с указателями Фортрана и функций/подпрограмм. У меня есть две функции, которые принимают массив в качестве аргумента. В f1 это a(n,n), в f2 это a(n*n). Когда я вызываю подпрограмму вручную, я могу сделать это с тем же массивом:

real :: a(5, 5)
call f1(a, 5)
call f2(a, 5)

Но когда я пытаюсь сделать это с помощью указателя, компилятор возвращает мне эту ошибку:

ptr => f2
       1
Error: Interface mismatch in procedure pointer assignment at (1): Type/rank missmatch in argument 'a'

Это можно обойти? Я думал об указателях, но там у меня та же проблема, для ее создания мне нужно знать количество измерений.

Для справки, вот полный код (надеюсь, он не слишком длинный..)

program ptrtest
implicit none

interface
    subroutine fnc(a, n)
        integer :: n
        real :: a(n, n)
    end subroutine

    subroutine f1(a, n)
        integer :: n
        real :: a(n, n)
    end subroutine

    subroutine f2(a, n)
        integer :: n

        real :: a(n*n)
    end subroutine
end interface

procedure(fnc), pointer :: ptr => null()
real :: a(5, 5)
real :: b(4, 4)

ptr => f1

call ptr(a, 5)
write(*,*) a

!this one does not work..

!ptr => f2
!
!call ptr(b, 4)
!write(*,*) b

call f2(b, 4)
write(*,*) b
end program

subroutine f1(a, n)
integer :: n
real :: a(n, n)
integer :: i

a = 1
end subroutine

subroutine f2(a, n)
integer :: n
real :: a(n*n)

a = 2
end subroutine

Я очень надеюсь, что есть способ сделать это. Я не могу переписать все подпрограммы, чтобы размеры массива каждый раз совпадали :/

С уважением, Каба.


person Cabadath    schedule 16.03.2012    source источник


Ответы (4)


Если я изменю ваш пример программы с использования явных интерфейсов (через блок интерфейса) на использование неявных интерфейсов (через объявления процедур без упоминания интерфейса), это, похоже, сработает для меня.

Поэтому удалите блок интерфейса, немного измените объявление ptr и добавьте объявления процедур для f1 и f2, например:

procedure(), pointer :: ptr => null()
procedure() :: f1, f2

(В качестве альтернативы вы можете использовать оператор external для f1 и f2 вместо оператора процедуры.)

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

person eriktous    schedule 16.03.2012
comment
Верно. Я не знал, что можно просто оставить скобки в операторе procedure() пустыми. В каждом найденном мной примере что-то было. Это делает все это значительно проще ._. Благодарность! Теперь мне просто нужно реализовать это в реальной жизни, и я надеюсь, что у меня все хорошо :) - person Cabadath; 17.03.2012

Вы передаете двумерный массив (:,:) подпрограмме, которая ожидает одномерный (:) массив. Вот почему Fortran жалуется. Один из способов обойти это — написать модуль с несколькими функциями с одним и тем же именем, который принимает разные массивы рангов в качестве аргументов, например:

  module test 

  interface fnc1
      module procedure fnc1_1d, fnc1_2d
  end interface

  contains

  subroutine fnc1_1d(ar,b,ar_out)

  real :: ar(:), ar_out(:)
  integer :: b

  ar_out = ar*b

  end subroutine fnc1_1d

  subroutine fnc1_2d(ar,b,ar_out)

  real :: ar(:,:), ar_out(:,:)
  integer :: b

  ar_out = ar*b

  end subroutine fnc1_2d

  end module test

теперь, когда вы вызываете fnc1, он будет вызывать fnc1_1d, если вы передаете массив 1D, и fnc_2d, если вы передаете массив 2d.

Program modify_value

use test

implicit none

real :: a1(5), a1_out(5)
real :: a2(5,5), a2_out(5,5)
integer :: j

a1 = 1.
a2 = 2.

call fnc1(a1,4,a1_out)
call fnc1(a2,4,a2_out)

open(1,file="out.txt")

write(1,'(5F5.1)') a1_out
do j=1,5
    write(1,'(5F5.1)') a2_out(j,:)
end do


close(1)

End Program

в out.txt сейчас:

4.0 4.0 4.0 4.0 4.0

8.0 8.0 8.0 8.0 8.0

8.0 8.0 8.0 8.0 8.0

8.0 8.0 8.0 8.0 8.0

8.0 8.0 8.0 8.0 8.0

8.0 8.0 8.0 8.0 8.0

Надеюсь это поможет.

person bananafish    schedule 16.03.2012
comment
Это отличный подход, если целью является выбор подпрограммы на основе ранга массива. Но мое понимание вопроса заключается в том, что цель состоит в том, чтобы передать массив двум подпрограммам, которые ожидают массивы разных рангов, а не автоматически выбирать подпрограмму из двух с соответствующим рангом. @Cabadath может уточнить, если я неправильно понимаю цель вопроса. - person M. S. B.; 17.03.2012
comment
@М. С. Б. Возможно, вы правы, возможно, я немного неправильно понял необходимость. Но тогда не могли бы вы просто написать вторую подпрограмму для приема 2D-массива вместо 1D? - person bananafish; 17.03.2012
comment
@MSB, ты прав. Дело в том, что у меня есть 3 подпрограммы, две из которых принимают массив 3d, а одна — массив 2d. Существует цикл более ~ 100 ^ 2, где метод всегда выбирается в предложении if внутри цикла. Что я хотел сделать, так это сохранить нужную подпрограмму в указателе (потому что он не меняется внутри цикла) и избавиться от предложения if, чтобы сэкономить время. - person Cabadath; 17.03.2012
comment
Я сомневаюсь, что процессорное время ветвления оператора IF вообще имеет значение по сравнению со временем вызова подпрограммы в ветвях и, предположительно, вычислениями с использованием массивов внутри подпрограмм. Если сравнение оператора IF сложно, вы можете выполнить его вне цикла и сохранить результат сравнения в логическом, хотя оптимизирующий компилятор, скорее всего, сделает это за вас. Метод в моем ответе исправит несоответствие рангов без копирования массива, что может быть дорого. - person M. S. B.; 17.03.2012

Изменить. Дело в том, что, как написано в других ответах, фактические и фиктивные аргументы не совпадают. Вместо того, чтобы пытаться обойти мое предложение, нужно создать указатель, который преобразует ранг массива, чтобы аргументы совпадали. Этот метод представляет собой «переназначение границ указателя» (см. также изменение размеров массива в fortran). Вот более полный пример:

module my_subs

contains

subroutine f1 (array)

   real, dimension (:,:), intent (in) :: array
   write (*, *) "f1:", ubound (array, 1), ubound (array, 2)

end subroutine f1


subroutine f2 (array)

   real, dimension (:), intent (in) :: array
   write (*, *) "f2:", ubound (array, 1)

end subroutine f2

end module my_subs


program test_ranks

   use my_subs
   real, dimension (2,2), target :: a2d
   real, dimension (:), pointer :: a4

   a2d = reshape ( [1., 2., 3., 4.], [2,2] )
   call f1 (a2d)

   a4 (1:4) => a2d
   call f2 (a4)

end program test_ranks

У меня есть подпрограммы в модуле, чтобы автоматически сделать интерфейс явным — я считаю, что это самая безопасная практика, поскольку она позволяет компилятору находить ошибки согласованности аргументов и необходима для «расширенных» функций Fortran 90, таких как размеры предполагаемой формы ( двоеточие), который я использовал.

Возможно, этот пример не отвечает потребностям вопроса, поскольку в нем не используется указатель на процедуры. Он передает один и тот же массив процедурам, ожидающим массивы разных рангов.

person M. S. B.    schedule 16.03.2012
comment
старый добрый equivalence может даже спасти от использования указателей - person Vladimir F; 18.03.2012
comment
В любом случае, следует быть осторожным, так как это, вероятно, не будет работать для несмежных массивов. - person Vladimir F; 19.03.2012
comment
Хорошая точка зрения. Вы можете заставить массивы быть непрерывными с помощью ключевого слова contiguous в объявлении. В некоторых случаях компилятор будет настаивать на этом. - person M. S. B.; 19.03.2012

В этой секции:

подпрограмма f2(a, n) целое :: n вещественное :: a(n*n)

вы используете массив 1D, когда fortran ожидает массив 2D в основной программе.

person armando    schedule 16.03.2012