В Fortran 90, каков хороший способ записи массива в текстовый файл построчно? - PullRequest
12 голосов
/ 29 июня 2011

Я новичок в 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 и т. д.

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

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

Ответы [ 3 ]

16 голосов
/ 30 июня 2011

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

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

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

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

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

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

генерирует строку rowfmt, которая в этом случае будет (762(1X,I6)), это формат, который вы будете использовать для печати, а опция RECL для OPEN устанавливает длину записи больше 7 * чисел + 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
10 голосов
/ 30 июня 2011

Это может быть очень обходной и трудоемкий способ сделать это, но в любом случае ... Вы можете просто напечатать каждый элемент массива отдельно, используя advance='no' (чтобы подавить вставку символа новой строки после того, что было напечатано)в вашем write заявлении.Как только вы закончите со строкой, вы используете «нормальный» оператор 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
4 голосов
/ 30 июня 2011

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

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

Приложение спустя несколько часов:

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

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

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

...