Использование MPI-IO для записи файлов в формате Fortran
Я пытаюсь сохранить решение, используя формат q-файла OVERFLOW-PLOT3D (определенный здесь: http://overflow.larc.nasa.gov/files/2014/06/Appendix_A.pdf). Для одной сетки, это в основном,
READ(1) NGRID
READ(1) JD,KD,LD,NQ,NQC
READ(1) REFMACH,ALPHA,REY,TIME,GAMINF,BETA,TINF, &
IGAM,HTINF,HT1,HT2,RGAS1,RGAS2, &
FSMACH,TVREF,DTVREF
READ(1) ((((Q(J,K,L,N),J=1,JD),K=1,KD),L=1,LD),N=1,NQ)
Все переменные являются числами двойной точности, за исключением NGRID, JD, KD, LD, NQ, NQC и IGAM, которые являются целыми числами. Мне нужно использовать MPI-IO для экспорта решения. Если я возьму очень простой пример с одним процессором, следующий код не работает, но я не понимаю, почему.
call mpi_file_open( mpi_comm_world, fileOut, mpi_mode_wronly + mpi_mode_create, &
mpi_info_null, mpi_fh, ierr )
offset = 0
call mpi_file_seek( mpi_fh, offset, mpi_seek_set, ierr )
call mpi_file_write( mpi_fh, (/NGRID,JD,KD,LD,NQ,NQC/), 6, mpi_integer, mstat, ierr )
call mpi_file_write( mpi_fh, (/REFMACH,ALPHA,REY,TIME,GAMINF,BETA,TINF/), 7, mpi_double_precision, mstat, ierr )
call mpi_file_write( mpi_fh, IGAM, 1, mpi_integer, mstat, ierr )
call mpi_file_write( mpi_fh, (/HTINF,HT1,HT2,RGAS1,RGAS2,FSMACH,TVREF,DTVREF/), 8, mpi_double_precision, mstat, ierr )
call mpi_file_write( mpi_fh, Q, NQ*JD*KD*LD, mpi_double_precision, mstat, ierr )
Tecplot не распознает формат. Однако, если я напишу простой не-MPI-код, такой как этот:
open(2, file=fileOut, form='unformatted', convert='little_endian')
write(2) NGRID
write(2) JD, KD, LD, NQ, NQC
write(2) REFMACH,ALPHA,REY,TIME,GAMINF,BETA,TINF, &
IGAM,HTINF,HT1,HT2,RGAS1,RGAS2, &
FSMACH,TVREF,DTVREF
write(2) ((((Q(J,K,L,N),J=1,JD),K=1,KD),L=1,LD),N=1,NQ)
все работает просто отлично. Что не так с моим кодом MPI-IO?? Большое спасибо за Вашу помощь!
Иоахим
NB: я не знаю, уместно ли это, но если я добавлю mpi_file_seek(offset) непосредственно перед последним оператором записи со смещением =144. Tecplot соглашается загрузить файл (но данные не читаются правильно). Это странно, потому что нормальное смещение должно быть 7 целых + 15 вещественных *8 = 148 байт...
РЕДАКТИРОВАТЬ: Ваш подход, @Jonathan Dursi, похоже, не работает с Tecplot по некоторым причинам. Что-то не так с приведенным ниже кодом? (упрощено для одного процессора)
call MPI_File_write(fileh, [4, ngrid, 4], 3, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [20, jd, kd, ld, nq, nqc, 20], 7, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [56], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [refmach,alpha,rey,time,gaminf,beta,tinf], 7, MPI_double_precision, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [56], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [4, IGAM, 4], 3, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [64], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [HTINF,HT1,HT2,RGAS1,RGAS2,FSMACH,TVREF,DTVREF], 8, MPI_double_precision, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [64], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [jd*kd*ld*nq*8], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, q, jd*kd*ld*nq, MPI_double_precision, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [jd*kd*ld*nq*8], 1, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
1 ответ
@francescalus прав - последовательные неотформатированные данные Fortran основаны на записях - что действительно очень хорошо для многих вещей, но ничто другое не использует их (даже MPI-IO в Fortran, который больше похож на C - файл просто большой поток недифференцированных байтов).
Давайте посмотрим на упрощенную версию вашей программы письма в вопросе:
program testwrite
integer, parameter:: ngrid=2
integer, parameter:: jd=4, kd=3, ld=2, nq=1, nqc=-1
integer, parameter :: refmach=1, alpha=2, rey=3, time=4, gaminf=5
integer, parameter :: beta=6, tinf=7
integer, dimension(jd,kd,ld,nq) :: q
q = 0
open(2, file='ftest.dat', form='unformatted', convert='little_endian')
write(2) NGRID
write(2) JD, KD, LD, NQ, NQC
write(2) REFMACH,ALPHA,REY,TIME,GAMINF,BETA,TINF
write(2) ((((Q(J,K,L,N),J=1,JD),K=1,KD),L=1,LD),N=1,NQ)
close(2)
end program testwrite
Выполнение этого и просмотр полученного двоичного файла с od
(Я сделал все целое число для ясности, глядя на двоичный файл):
$ gfortran -o fwrite fwrite.f90
$ ./fwrite
$ od --format "d" ftest.dat
0000000 4 2 4 20
0000020 4 3 2 1
0000040 -1 20 28 1
0000060 2 3 4 5
0000100 6 7 28 96
0000120 0 0 0 0
*
0000260 96
0000264
Например, мы видим в начале целое число ngrid (2), добавленное 4/4 - размер записи в байтах. Затем, добавленные 20/20, мы видим 5 целых чисел (5*4 байта) 4,3,2,1,-1 - jd, kd, ld, nq, nqc. В конце мы видим группу нулей, добавленных 96 (= 4 байта / целое число *4*3*2*1), что представляет собой q. (Обратите внимание, что не существует стандарта, определяющего это поведение, но я не знаю ни одного крупного компилятора Фортрана, который не делает этого таким образом; однако, когда записи становятся больше, чем можно описать 4-байтовым целым числом, поведение начинает отличаются.
Мы можем использовать следующую простую программу для проверки файла данных:
program testread
implicit none
integer :: ngrid
integer :: jd, kd, ld, nq, nqc
integer :: refmach, alpha, rey, time, gaminf
integer :: beta, tinf
integer :: j, k, l, n
integer, allocatable, dimension(:,:,:,:) :: q
character(len=64) :: filename
if (command_argument_count() < 1) then
print *,'Usage: read [filename]'
else
call get_command_argument(1, filename)
open(2, file=trim(filename), form='unformatted', convert='little_endian')
read(2) NGRID
read(2) JD, KD, LD, NQ, NQC
read(2) REFMACH,ALPHA,REY,TIME,GAMINF,BETA,TINF
allocate(q(jd, kd, ld, nq))
read(2) ((((Q(J,K,L,N),J=1,JD),K=1,KD),L=1,LD),N=1,NQ)
close(2)
print *, 'Ngrid = ', ngrid
print *, 'jd, kd, ld, nq, nqc = ', jd, kd, ld, nq, nqc
print *, 'q: min/mean/max = ', minval(q), sum(q)/size(q), maxval(q)
deallocate(q)
endif
end program testread
и бег дает
$ ./fread ftest.dat
Ngrid = 2
jd, kd, ld, nq, nqc = 4 3 2 1 -1
q: min/mean/max = 0 0 0
Достаточно просто.
Так что это поведение довольно легко имитировать в MPI-IO. Здесь действительно три части - заголовок Q, который, как я предполагаю, будет распределен (скажем, с помощью подмассивов MPI) и нижний колонтитул (который является просто сокращением массива).
Итак, давайте посмотрим на программу MPI-IO на Фортране, которая будет делать то же самое:
program mpiwrite
use mpi
implicit none
integer, parameter:: ngrid=2
integer, parameter:: jd=3, kd=3, ld=3, nlocq=3, nqc=-1
integer :: nq
integer, parameter :: refmach=1, alpha=2, rey=3, time=4, gaminf=5
integer, parameter :: beta=6, tinf=7
integer, dimension(jd,kd,ld,nlocq) :: q
integer :: intsize
integer :: subarray
integer :: fileh
integer(kind=MPI_Offset_kind) :: offset
integer :: comsize, rank, ierr
call MPI_Init(ierr)
call MPI_Comm_size(MPI_COMM_WORLD, comsize, ierr)
call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
nq = nlocq * comsize
q = rank
! create a subarray; each processor gets its own q-slice of the
! global array
call MPI_Type_create_subarray (4, [jd, kd, ld, nq], [jd, kd, ld, nlocq], &
[0, 0, 0, nlocq*rank], &
MPI_ORDER_FORTRAN, MPI_INTEGER, subarray, ierr)
call MPI_Type_commit(subarray, ierr)
call MPI_File_open(MPI_COMM_WORLD, 'mpi.dat', &
MPI_MODE_WRONLY + MPI_MODE_CREATE, &
MPI_INFO_NULL, fileh, ierr )
! the header size is:
! 1 field of 1 integer ( = 4*(1 + 1 + 1) = 12 bytes )
! +1 field of 5 integers( = 4*(1 + 5 + 1) = 28 bytes )
! +1 field of 7 integers( = 4*(1 + 7 + 1) = 36 bytes )
! +first bookend of array size = 4 bytes
offset = 12 + 28 + 36 + 4
! rank 1 writes the header and footer
if (rank == 0) then
call MPI_File_write(fileh, [4, ngrid, 4], 3, MPI_INTEGER, &
MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [20, jd, kd, ld, nq, nqc, 20], 7, MPI_INTEGER, &
MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, &
[28, refmach, alpha, rey, time, gaminf, beta, tinf, 28],&
9, MPI_INTEGER, MPI_STATUS_IGNORE, ierr)
call MPI_File_write(fileh, [jd*kd*ld*nq*4], 1, MPI_INTEGER, &
MPI_STATUS_IGNORE, ierr)
call MPI_File_seek(fileh, offset+jd*kd*ld*nq*4, MPI_SEEK_CUR, ierr)
call MPI_File_write(fileh, [jd*kd*ld*nq*4], 1, MPI_INTEGER, &
MPI_STATUS_IGNORE, ierr)
endif
! now everyone dumps their part of the array
call MPI_File_set_view(fileh, offset, MPI_INTEGER, subarray, &
'native', MPI_INFO_NULL, ierr)
call MPI_File_write_all(fileh, q, jd*kd*ld*nlocq, MPI_INTEGER, &
MPI_STATUS_IGNORE, ierr)
call MPI_File_close(fileh, ierr)
CALL MPI_Finalize(ierr)
end program mpiwrite
В этой программе процесс 0 отвечает за запись заголовка и полей записи. Он начинается с записи трех записей заголовка, каждая из которых добавляется по длине записи в байтах; и затем он записывает два форзаца для большого массива Q.
Затем каждый ранг устанавливает представление файла, чтобы сначала пропустить заголовок, а затем описать только его часть глобального массива (заполненного здесь только его номером ранга), и записать свои локальные данные. Все это будут непересекающиеся фрагменты данных.
Итак, давайте попробуем это с парой разных размеров:
$ mpif90 -o mpifwrite mpifwrite.f90
$ mpirun -np 1 ./mpifwrite
$ ./fread mpi.dat
Ngrid = 2
jd, kd, ld, nq, nqc = 3 3 3 3 -1
q: min/mean/max = 0 0 0
$ od --format="d" mpi.dat
0000000 4 2 4 20
0000020 3 3 3 3
0000040 -1 20 28 1
0000060 2 3 4 5
0000100 6 7 28 324
0000120 0 0 0 0
*
0000740 0 324
0000750
$ mpirun -np 3 ./mpifwrite
$ ./fread mpi.dat
Ngrid = 2
jd, kd, ld, nq, nqc = 3 3 3 9 -1
q: min/mean/max = 0 1 2
$ od --format="d" mpi.dat
0000000 4 2 4 20
0000020 3 3 3 9
0000040 -1 20 28 1
0000060 2 3 4 5
0000100 6 7 28 972
0000120 0 0 0 0
*
0000620 0 1 1 1
0000640 1 1 1 1
*
0001320 1 1 2 2
0001340 2 2 2 2
*
0002020 2 2 2 0
0002040 0 0 0 0
*
0002140 0 0 0 972
0002160
какой выход мы ожидаем. Расширение объектов до нескольких типов данных или нескольких сеток является относительно простым.