Fortran - Разпределяем масив от разпределяем производен тип

Така че търсих през последните 3-4 дни и не можах да намеря отговора на този проблем. Това е свързано с разпределяеми масиви, които са от специфичен производен тип. Всичко това е част от програма за решаване на изчислителна динамика на флуидите. Действителното приложение обаче няма значение. Позволете ми да дам малко (бърз) програмен контекст.

Да кажем, че имаме прост модул, който дефинира производен тип с фиксиран размер и основната програма, която разпределя масив от няколко типа:

module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient of variables in x,y,z direction
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: elements

   elements = 100
   allocate(array(elements))

end program

Този кодов фрагмент може, разбира се, да бъде компилиран с помощта на всеки компилатор. Тъй като размерът на array_t е фиксиран и известен по време на компилиране, ние трябва само да разпределим структурата array в един ред (дефинирайки броя на повторенията на array_t вътре в структурата).

Когато става дума за местоположения в паметта, променливите ще се съхраняват, както следва:

array(1)%variable(1) ! element 1
array(1)%variable(2)
...
...
array(1)%gradient(1,1) ! the rest of this 2D array will be written column-major in fortran
array(1)%gradient(2,1)
array(1)%gradient(3,1)
...
...
array(1)%limiter(1)
array(1)%limiter(2)
...
...
array(2)%variable(1) ! element 2
array(2)%variable(2)
...
...

В този пример задаваме параметъра equations=10. В решаващия инструмент винаги оставяме този размер максимален (10): всички производни типове имат максималното измерение, което може да изисква решаващият инструмент. За съжаление, това означава, че може да отделим повече памет, отколкото всъщност ни е необходима - някои симулации може да се нуждаят само от 5 или 6 уравнения вместо 10. В допълнение, фактът, че размерът на извлечения тип остава фиксиран на размер 10, прави решаването по-бавно, когато решаваме за по-малко уравнения - неизползваните места в паметта ще намалят честотната лента на паметта.

Това, което искам да направя, е да използвам производни типове, които имат атрибута allocable. По този начин мога да разпределя структурите, като използвам само необходимия брой уравнения (т.е. измерения на array_t), които ще бъдат дефинирани по време на изпълнение (а не по време на компилиране) и ще се променят въз основа на параметрите на симулацията.

Разгледайте следния кодов фрагмент:

module types

   integer, save:: equations

   type array_t
      double precision, allocatable :: variable(:) ! variables to solve
      double precision, allocatable :: gradient(:,:) ! gradient
      double precision, allocatable :: limiter(:) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: i,elements

   equations = 10
   elements = 100
   allocate(array(elements))
   do i=1,elements
      allocate(array(i)%variable(equations))
      allocate(array(i)%gradient(equations,3))
      allocate(array(i)%limiter(equations))
   enddo

end program

Засега това е единственият начин, по който успях да го накарам да работи. Решавателят работи и се събира, което означава, че синтаксисът е не само компилируем, но и еквивалентен на използването на фиксиран размер.

Решавателят обаче е значително по-бавен с този подход, дори за същия брой уравнения.

Това означава, че има разместване на паметта. Въз основа на измерените времена на изпълнение изглежда, че променливите не се съхраняват по същия начин, както при използване на фиксиран размер.

При втория подход, как променливите се съхраняват в глобалната памет? Искам да постигна същия модел като първия подход. Чувствам се като първия ред, който разпределя структурата

allocate(array(elements))

не знае какво да разпредели, така че или разпределя голяма част от паметта (за да пасне на разпределяемия тип, който ще дойде по-късно), или просто разпределя индексите array(1) към array(elements) и нищо друго (което означава, че действителният съдържанието на структурата се съхранява по-късно, вътре в цикъла).

Как мога да накарам втория подход да съхранява променливите като първия?

РЕДАКТИРАНЕ #1

Тъй като параметризираните производни типове получиха известно сцепление, прецених, че би било полезно да публикувам някои допълнителни подробности.

Параметризираните производни типове ще работят в сценарии, при които масивът е разпределен вътре в основната програма (като примерния код, който публикувах).

Моят случай от „реалния свят“ обаче прилича повече на следното:

(file_modules.f90)
module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient pf variables
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

module flow_solution
   use types
   type (array_t), allocatable, save :: cell_solution(:)
end module

(file_main.f90)
program test

   use flow_solution

   implicit none
   integer :: elements

   elements = 100
   allocate(cell_solution(elements))

end program

Те (както бихте очаквали) се компилират и свързват отделно чрез Makefile. Ако използвах параметризиран производен тип, модулният файл не може да бъде компилиран, тъй като размерът 'n' на типа не е известен по време на компилиране.

РЕДАКТИРАНЕ #2

Бях посъветван да предоставя примери за работещ и неработещ код с параметризирани производни типове.

Работещ пример:

module types

   integer, parameter  :: equations=10

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   elements = 100 
   allocate(flowsol(elements))

end program

Неработещ пример:

module types

   integer, save       :: equations

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   equations = 10
   elements = 100 
   allocate(flowsol(elements))

end program

Грешка на компилатора (ifort):

test.f90(16): error #6754: An automatic object must not appear in a SAVE statement or be declared with the SAVE attribute.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
test.f90(16): error #6841: An automatic object must not appear in the specification part of a module.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
compilation aborted for test.f90 (code 1)

Трябва ли да декларирам/разпределя масивите по различен начин?


person SlapGas Unseen    schedule 21.11.2019    source източник
comment
Вероятно търсите параметризираните производни типове на Fortran. Нямам време да пиша отговор, но има няколко въпроса и отговора по темата тук в SO и много други ресурси в мрежата.   -  person High Performance Mark    schedule 21.11.2019
comment
Като се има предвид нещо като type(array_t(:)), allocatable :: cell_solution(:), съответното изявление за разпределение allocate(array_t(5) :: cell_solution(100)) би изглеждало подходящо. Това е мястото, където array_t е дължина-параметризиран тип (не е показано в този коментар). Такива параметризирани по дължина типове изглеждат приложими тук.   -  person francescalus    schedule 21.11.2019
comment
Благодаря ви за приноса! Вашето предложение е компилируемо. Ще трябва обаче да го внедря в солвъра и да видя дали работи правилно. Работата е там, че имах проблеми с разместването на паметта, използвайки подхода на разпределяемите типове. Надявам се, че този подход съхранява променливите по правилния начин.   -  person SlapGas Unseen    schedule 21.11.2019
comment
Ако сте загрижени за оформлението на производните типове в паметта, може да искате да проучите израза sequence.   -  person High Performance Mark    schedule 21.11.2019


Отговори (1)


Проблемната линия е

type(array_t(equations)), allocatable, save :: flowsol(:)

Тук искате параметризиран с дължина тип да бъде записан обект. Както обектите на компилатора, записан обект не може да бъде автоматичен обект. Освен това не можете да имате автоматичен обект в обхвата на модула.

Сега, защо flowsol е автоматичен обект? Това е автоматично, защото типът му е array_t(equations): equations не е константа. Вместо това искате параметърът тип да бъде отложен (каквато е формата на масива):

type(array_t(:)), allocatable, save :: flowsol(:)

Когато стигнете до разпределяне на такъв обект, трябва да предоставите стойността на параметъра тип дължина:

allocate(array_t(equations) :: flowsol(elements))

Както виждате, когато equations е (наименована) константа, нещата са по-щастливи. В такъв случай не се декларира автоматичен обект.

person francescalus    schedule 21.11.2019
comment
За съжаление, тази опция също е много бавна. На пръв поглед ми се струва, че е дори по-бавен от подхода с разпределение, но нямах време да ги сравня. Сигурен съм, че определено е значително по-бавен от оригиналния подход (този, който има фиксирани уравнения = 10 като параметър). Тъй като тази структура е част от сравнително голям решаващ инструмент, не мисля, че е възможно да публикувам пример за код, който показва забавянето, за което имам предвид. - person SlapGas Unseen; 21.11.2019
comment
За контекст, единственото нещо, което променям е, че промених променливата на уравненията в цяло число, запазване вместо цяло число, параметър. Съответният тип е параметризиран с дължина n и разпределението при инициализацията на солвъра е както предложихте. Имайте предвид, че не променям никаква друга структура, нито използвам този тип някъде другаде. Дори тогава решаващият проблем е почти 100% по-бавен. - person SlapGas Unseen; 21.11.2019
comment
Голямото забавяне е неочаквано, но както казвате, няма какво да се разглежда в обхвата на SO въпрос. Може би като тест бихте могли да направите типа kind-параметризиран (може би дори като 10), за да видите дали все още има забавяне. - person francescalus; 21.11.2019