Фортран: выбор ранга размещаемого массива

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

Что я делаю в настоящее время, так это:

program main
implicit none
integer :: rank,n=10
real*8, allocatable :: A1(:)
real*8, allocatable :: A2(:,:)
read (*,*) rank

if (rank.eq.1) then
    allocate (A1(n))
else if (rank.eq.2) then
    allocate (A2(n,n))
end if

! operate on the array
if (rank.eq.1) then
    call work(A1)
else if (rank.eq.2) then
    call work(A2)
end if

end program

Все было бы намного проще, если бы я каким-то образом мог выбрать ранг A, так как тогда операторы if не нужны. Возможно, это невозможно, но вся помощь приветствуется.


person Lun    schedule 27.06.2016    source источник
comment
Невозможно выделить ранг массива во время выполнения, как вы, кажется, знаете. Вы показали нам фрагмент, указывающий на один из возможных кладжей, чтобы обойти это. Есть и другие, такие как разумное использование include; вы даже можете обернуть массив ранга 1 внутри производного типа и написать операции для управления им, как если бы он имел ранг n (выбирая n во время выполнения). Но если вы объясните нам, почему вам это нужно, мы, возможно, сможем рассказать вам, как написать вашу программу без этой возможности.   -  person High Performance Mark    schedule 27.06.2016
comment
Большое спасибо за быстрый ответ Марк. Причина, по которой я хочу это сделать, заключается в том, что я думаю, что если бы это было возможно, мой код выглядел бы намного чище (без всех операторов if) и его было бы легче модифицировать. Но, возможно, это слишком много для того, чем я занимаюсь. Я хочу, чтобы моя программа могла решать одномерное зависящее от времени уравнение Шредингера (TDSE), двумерное TDSE, а массив A мог быть размерностью волновой функции. У вас есть ссылки на решение, которое вы предложили с производным типом?   -  person Lun    schedule 27.06.2016


Ответы (2)


Следующий стандарт Фортрана (2015 г.) имеет конструкцию select rank, аналогичную select case. В моем примере используется конструкция select case для встроенной функции rank фиктивной переменной предполагаемого ранга.

    module my_type

  use, intrinsic :: iso_fortran_env, &
       ip => INT32, &
       wp => REAL64

  implicit none
  private
  public :: MyType

  type MyType
     real (wp)              :: rank0
     real (wp), allocatable :: rank1(:)
     real (wp), allocatable :: rank2(:,:)
     real (wp), allocatable :: rank3(:,:,:)
   contains
     procedure :: create => create_my_type
     procedure :: destroy => destroy_my_type
  end type MyType

contains

  subroutine create_my_type(this, array)
    ! calling arguments
    class (MyType), intent (in out) :: this
    real (wp),      intent (in)     :: array(..) !! Assumed-rank dummy variable

    ! local variables
    integer (ip), allocatable :: r(:)

    select case(rank(array))
    case (0)
       return
    case (1)
       r = shape(array)
       allocate( this%rank1(r(1)) )
    case (2)
       r = shape(array)
       allocate( this%rank2(r(1), r(2)) )
    case (3)
       r = shape(array)
       allocate( this%rank3(r(1), r(2), r(3)) )
    case default
       error stop 'array must have rank 0,1,2, or 3'
    end select

    ! Release memory
    if (allocated(r)) deallocate( r )

  end subroutine create_my_type


  subroutine destroy_my_type(this)
    ! calling arguments
    class (MyType), intent (in out) :: this

    if (allocated(this%rank1)) deallocate( this%rank1 )
    if (allocated(this%rank2)) deallocate( this%rank2 )
    if (allocated(this%rank3)) deallocate( this%rank3 )

  end subroutine destroy_my_type

end module my_type

program main

  use, intrinsic :: iso_fortran_env, only: &
       ip => INT32, &
       wp => REAL64

  use my_type, only: &
       MyType

  implicit none

  type (MyType) :: foo
  real (wp)     :: a0, a1(42), a2(42,42), a3(42,42,42)

  print *, rank(a0)
  print *, rank(a1)
  print *, rank(a2)
  print *, rank(a3)

  ! Allocate array of rank 3
  call foo%create(a3)

  print *, rank(foo%rank3)
  print *, shape(foo%rank3)
  print *, size(foo%rank3)

  ! Release memory
  call foo%destroy()

end program main
person jlokimlin    schedule 27.06.2016
comment
Да, в F2015 можно использовать массивы предполагаемых рангов. Но поскольку их использование в полностью программе на Фортране очень ограничено, они не слишком хороши для управления потоком программы. То есть значение array в create_my_type не находится в свободном доступе, поэтому можно получить почти такой же контроль, как если бы вы просто передавали буквальную константу 3. - person francescalus; 27.06.2016
comment
@francescalus F2015 добавляет возможности, выходящие за рамки дополнительной TS совместимости C. Модель select rank аналогична модели select type (хотя она имеет некоторые отличия, связанные с атрибутами объекта) — она делает доступным имя в дочернем блоке, который имеет ранг, соответствующий оператору выбора ранга case, который возглавляет блок. Затем вы можете ссылаться/определять этот объект с его конкретным рангом, как если бы это был обычный массив. Но использование этой функции F201X в любом случае сегодня довольно гипотетично. - person IanH; 27.06.2016
comment
Спасибо за дополнительную информацию, @IanH. Я должен убедиться, что моя копия проекта/предложения актуальна. Надеюсь, мой первый комментарий по-прежнему имеет смысл, поскольку он относится к примеру в этом ответе, а не к более общему select rank способу. - person francescalus; 27.06.2016
comment
Спасибо за ответ и все комментарии. Я новичок в Fortran, но поскольку F2015 совсем новый, можно ли предположить, что он будет содержать ошибки и не будет доступен на всех системах и кластерах? - person Lun; 28.06.2016

Объявите массив рангом три. Если требуется массив более низкого ранга, выделите соответствующие конечные измерения, чтобы они имели размер один.

real, allocatable :: array(:,:,:)
...
select case (desired_rank)
case (1) ; allocate(array(n,1,1))
case (2) ; allocate(array(n,n,1))
case (3) ; allocate(array(n,n,n))
case default ; error stop 'bad desired rank'
end select

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

person IanH    schedule 27.06.2016
comment
Спасибо, но не уверен, что это полностью решает мою проблему. Я хочу иметь процедуру work (содержит подпрограммы, зависящие от ранга входного массива), которая при воздействии на array выдает желаемый результат. Если я использую раздел массива для получения непрерывного фрагмента массива, мне нужно сохранить фрагмент массива, прежде чем я смогу использовать work? Если я последую вашему альтернативному предложению, мне придется определить work таким образом, чтобы он распознавал три разных случая, в основном сдвигая операторы if моего исходного сообщения в подпрограмму. Но, может быть, это единственный способ, не прибегая к select rank. - person Lun; 28.06.2016
comment
В настоящее время у вас есть три процедуры за одним общим интерфейсом. Я не знаю, что означает сохранение фрагмента массива. Вы можете взять непрерывный срез массива ранга один, используя синтаксис array(:,i,j). В некоторых случаях обработка массива более высокого ранга просто включает итерацию по последнему рангу - эквивалент операторов «если» в этом случае становится неявным в одной итерации, если такая схема используется для встраивания массивов более низкого ранга. Если бы вы не вызывали свои процедуры через общий интерфейс, вы также могли бы использовать ассоциацию последовательностей. - person IanH; 28.06.2016
comment
Спасибо еще раз. Но предположим, что у меня есть две процедуры (в зависимости от ранга array) за одним универсальным интерфейсом, который выводит массив, содержащий каждый arrayэлемент, умноженный на число. Чтобы сделать то, что вы предлагаете, мне нужно либо сделать операторы if для выбора среза, либо мне нужно перебрать индекс внешнего ранга из основной программы и вызвать процедуру (что нежелательно, поскольку я хочу распараллелить внешний цикл). Если бы только array в case (1) вашего ответа имел первый ранг, то все бы работало легко. :) - person Lun; 28.06.2016