web-dev-qa-db-fra.com

Dans Fortran 90, quelle est la bonne façon d'écrire un tableau dans un fichier texte, par ligne?

Je suis nouveau sur Fortran, et j'aimerais pouvoir écrire un tableau bidimensionnel dans un fichier texte, de manière rangée (espaces entre les colonnes et chaque ligne sur sa propre ligne). J'ai essayé ce qui suit, et cela semble fonctionner dans l'exemple simple suivant:

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

Comme je l'ai dit, cela semble fonctionner correctement dans cet exemple simple: le fichier texte résultant, aoutput.txt, contient les numéros 1-762 sur la ligne 1, les numéros 763-1524 sur la ligne 2, etc. sur.

Mais, lorsque j'utilise les idées ci-dessus (c'est-à-dire les dernières avant-dernières, avant-dernières, avant-dernières et avant-dernières lignes de code ci-dessus) dans un programme plus compliqué, je lance Dans les ennuis; chaque ligne n'est délimitée (par une nouvelle ligne) que par intermittence, semble-t-il. (Je n'ai pas posté, et ne posterai probablement pas, ici tout mon programme/script compliqué - car il est assez long.) Le manque de délimiteurs de lignes cohérents dans mon programme/script compliqué suggère probablement un autre bogue dans mon code, pas avec la routine d'écriture dans un fichier de quatre lignes ci-dessus, car l'exemple simple ci-dessus semble fonctionner correctement. Pourtant, je me demande, pouvez-vous m'aider à penser s'il existe une meilleure routine d'écriture dans le texte par ligne que je devrais utiliser ?

Merci beaucoup pour votre temps. J'apprécie vraiment cela.

12
Andrew

Il y a quelques problèmes ici.

L'essentiel est que vous ne devez pas utiliser le texte comme format de données pour des blocs de données importants. C'est grand et c'est lent. La sortie de texte est bonne pour quelque chose que vous allez lire vous-même; vous n'allez pas vous asseoir avec une impression de 3,81 millions d'entiers et les feuilleter. Comme le montre le code ci-dessous, la sortie de texte correcte est environ 10 fois plus lente et 50% plus grande que la sortie binaire. Si vous passez à des valeurs à virgule flottante, l'utilisation de chaînes ascii comme format d'échange de données pose des problèmes de perte de précision. etc.

Si votre objectif est d'échanger des données avec matlab, il est assez facile d'écrire les données dans un format que matlab peut lire; vous pouvez utiliser l'API matOpen/matPutVariable de matlab, ou simplement l'écrire en tant que tableau HDF5 que matlab peut lire. Ou vous pouvez simplement écrire le tableau en binaire Fortran brut comme ci-dessous et avoir matlab le lire .

Si vous devez utiliser ascii pour écrire d'énormes tableaux (ce qui, comme mentionné, est une mauvaise et lente idée), vous rencontrez des problèmes avec des longueurs d'enregistrement par défaut dans les E/S répertoriées. Le mieux est de générer au moment de l'exécution une chaîne de format qui décrit correctement votre sortie, et le plus sûr pour les lignes aussi grandes (~ 5000 caractères!) Est de définir explicitement la longueur d'enregistrement à quelque chose de plus grand que ce que vous allez imprimer de sorte que la bibliothèque fortran IO ne rompt pas utilement les lignes pour vous.

Dans le code ci-dessous,

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

génère la chaîne rowfmt qui dans ce cas serait (762(1X,I6)) qui est le format que vous utiliserez pour l'impression, et l'option RECL sur OPEN définit la longueur d'enregistrement comme étant quelque chose de plus grand que 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
16
Jonathan Dursi

Cela peut être un moyen très détourné et long de le faire, mais de toute façon ... Vous pouvez simplement imprimer chaque élément du tableau séparément, en utilisant advance='no' (pour supprimer l'insertion d'un caractère de nouvelle ligne après ce qui était imprimé) dans votre instruction write. Une fois que vous avez terminé avec une ligne, vous utilisez une instruction write 'normale' pour obtenir le caractère de nouvelle ligne et recommencez sur la ligne suivante. Voici un petit exemple:

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

Lorsque vous exécutez ce programme, la sortie est la suivante:

 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
10
canavanin

L'utilisation d'un "*" est dirigée par liste IO - Fortran prendra les décisions pour vous. Certains comportements ne sont pas spécifiés. Vous pouvez gagner plus de contrôle en utilisant une instruction de format. Si vous le souhaitez identifier positivement les limites des lignes, vous écrivez un symbole marqueur après chaque ligne. Quelque chose comme:

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

Addendum quelques heures plus tard:

Peut-être qu'avec de grandes valeurs de numcols, les lignes sont trop longues pour certains programmes que vous utilisez pour examiner le fichier? Pour l'instruction de sortie, essayez:

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

qui divisera chaque ligne de la matrice, si elle a plus de 10 colonnes, en plusieurs lignes plus courtes dans le fichier.

4
M. S. B.