В Fortran 90, что является хорошим способом записи массива в текстовый файл, по-разному?

Я новичок в Fortran, и мне хотелось бы написать двумерный массив в текстовом файле по-разному (пробелы между столбцами и каждой строкой в ​​собственной строке). Я пробовал следующее и, похоже, работает на следующем простом примере:

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace")
  DO i=1,numrows
    WRITE(12,*) (a(i,j), j=1,numcols)
  END DO
END PROGRAM test3

Как я уже сказал, в этом простом примере это прекрасно работает: результирующий текстовый файл aoutput.txt содержит номера 1-762 в строке 1, номера 763-1524 в строке 2, и т.д.

Но, когда я использую приведенные выше идеи (т.е. последние пятый-последний, четвертый-последний, третий-последний и второй-последний строки кода выше) в более сложной программе, Я столкнулся с бедой; Кажется, что каждая строка делится (по новой строке) только с перерывами. (Я не писал и, вероятно, не буду публиковать здесь всю свою сложную программу / script - потому что она довольно длинная.) Отсутствие последовательных разделителей строк в моей сложной программе /script, вероятно, предполагает еще одну ошибку в моей кода, а не с четырехстрочной программой для записи в файл выше, поскольку приведенный выше простой пример работает нормально. Тем не менее, мне интересно, можете ли вы, пожалуйста, помочь мне подумать, есть ли лучшая строка-сценарий для записи в текстовый файл, который я должен использовать?

Большое спасибо за ваше время. Я действительно ценю это.

Ответ 1

Здесь есть несколько вопросов.

Фундаментальным является то, что вы не должны использовать текст в качестве формата данных для значительных фрагментов данных. Он большой и медленный. Вывод текста хорош для того, что вы собираетесь читать сами; вы не собираетесь садиться с распечаткой 3,81 миллиона целых чисел и перелистывать их. Как показывает приведенный ниже код, правильный вывод текста примерно на 10 раз медленнее и на 50% больше, чем двоичный вывод. Если вы переходите к значениям с плавающей запятой, возникают проблемы с точной потерей при использовании строк ascii в качестве формата обмена данными. и др.

Если ваша цель - обмен данными с помощью Matlab, довольно легко записать данные в формат, который может читать MATLAB; вы можете использовать API matOpen/matPutVariable из Matlab или просто записать его как массив HDF5, который может читать Matlab. Или вы можете просто записать массив в необработанном двоичном формате Fortran, как показано ниже, и matlab прочитать его.

Если вы должны использовать ascii для записи огромных массивов (что, как уже упоминалось, представляет собой плохую и медленную идею), тогда у вас возникают проблемы с размерами записей по умолчанию при редактировании ввода-вывода со списком. Лучше всего генерировать во время выполнения строку формата, которая правильно описывает ваш вывод, и безопаснее всего для таких больших (~ 5000 символов!) Строк - это установить длину записи явно на нечто большее, чем то, что вы будете распечатывать так что библиотека forran IO не поможет разбить линии для вас.

В приведенном ниже коде

  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'

генерирует строку rowfmt, которая в этом случае будет (762(1X,I6)), который является форматом, который вы будете использовать для распечатки, а опция RECL для OPEN устанавливает длину записи как нечто большее, чем 7 * numcols + 1.

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a
  CHARACTER(LEN=30) :: rowfmt
  INTEGER :: txtclock, binclock
  REAL    :: txttime, bintime

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  CALL tick(txtclock)
  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace", &
       RECL=(7*numcols+10))
  DO i=1,numrows
    WRITE(12,FMT=rowfmt) (a(i,j), j=1,numcols)
  END DO
  CLOSE(UNIT=12)
  txttime = tock(txtclock)

  CALL tick(binclock)
  OPEN(UNIT=13, FILE="boutput.dat", ACTION="write", STATUS="replace", &
       FORM="unformatted")
  WRITE(13) a
  CLOSE(UNIT=13)
  bintime = tock(binclock)

  PRINT *, 'ASCII  time = ', txttime
  PRINT *, 'Binary time = ', bintime

CONTAINS

    SUBROUTINE tick(t)
        INTEGER, INTENT(OUT) :: t

        CALL system_clock(t)
    END SUBROUTINE tick

    ! returns time in seconds from now to time described by t
    REAL FUNCTION tock(t)
        INTEGER, INTENT(IN) :: t
        INTEGER :: now, clock_rate

        call system_clock(now,clock_rate)

        tock = real(now - t)/real(clock_rate)
    END FUNCTION tock
END PROGRAM test3

Ответ 2

Это может быть очень крутой и трудоемкий способ сделать это, но в любом случае... Вы можете просто распечатать каждый элемент массива отдельно, используя advance='no' (чтобы подавить вставку символа новой строки после того, что было напечатано) в вашем заявлении write. Когда вы закончите с линией, вы используете оператор "normal" write, чтобы получить символ новой строки, и начните снова на следующей строке. Вот небольшой пример:

program testing

implicit none

integer :: i, j, k

k = 1

do i=1,4
   do j=1,10
      write(*, '(I2,X)', advance='no') k
      k = k + 1
   end do
   write(*, *) ''  ! this gives you the line break
end do

end program testing

При запуске этой программы вывод выглядит следующим образом:

 1  2  3  4  5  6  7  8  9 10  
11 12 13 14 15 16 17 18 19 20  
21 22 23 24 25 26 27 28 29 30  
31 32 33 34 35 36 37 38 39 40

Ответ 3

Использование "*" - это список-ориентированный IO - Fortran примет решения для вас. Некоторые виды поведения не указаны. Вы можете получить больше контроля с помощью оператора формата. Если вы хотите позитивно определить границы строк, вы пишете символ маркера после каждой строки. Что-то вроде:

  DO i=1,numrows
    WRITE(12,*) a(i,:)
    write (12, '("X")' )
  END DO

Добавление через несколько часов:

Возможно, с большими значениями numcols строки слишком длинны для некоторых программ, которые вы используете для проверки файла? Для оператора вывода попробуйте:

WRITE(12, '( 10(2X, I11) )' ) a(i,:)

который разбивает каждую строку матрицы, если она содержит более 10 столбцов, на несколько более коротких строк в файле.