Fortran 3

Da CdM_unimore.

LEZIONE 17


INPUT/OUTPUT

Istruzioni necessarie per la lettura dei File da disco.

Vediamo ora com'è possibile creare un programma che prenda i dati d'ingresso da un file di testo(.txt), li elabori e li restituisca ad un file output anch'esso di testo (.txt). Viene scritto un programma per inizializzare il lavoro; il programma viene riportato di seguito e salvato col nome iofile.for.

     program iofile
     implicit none
     double precision definiscimi
     open(UNIT=1, FILE='output.txt', STATUS='new', FORM='FORMATTED')
     open(UNIT=2, FILE='input.txt' , STATUS='old', FORM='FORMATTED')
     read(UNIT=1 ,FMT=*)definiscimi
     write(UNIT=2 ,FMT=*)'scrivimi'
     close(UNIT=1)
     close(UNIT=2)
     write(*,*) 'Esecuzione corretta'
     stop
     end

I comandi standard di input ed output sono write (*,*) e read(*,*); gli asterischi (*,*) rappresentano rispettivamente l'indicazione dell'unità di default e il formato. Per il write il primo asterisco consiste nell'uscita a video del terminale mentre per il read è l'input da tastiera. In questo caso invece sono stati specificati l'unità per il read e per il write cosicché il programma potesse leggere l'input dall'unità 1 e scrivere l'output nell'unità 2.


Il valore di unità UNIT=... può essere un numero da 0 a 99 (esclusi 5-6 che sono gli standard impliciti di input e output) sostituendo quindi * con un numero. Se non viene specificata alcuna unità e si lascia l'asterisco allora vuol dire che tutti i file di input devono essere inseriti durante l'esecuzione del programma stesso e che l'output viene fornito a video allo stesso modo durante l'esecuzione del programma.

Occorrerà aprire e dare un nome al file, attraverso il comando OPEN:


    open(UNIT=numero unità, FILE='nome_file.txt',STATUS='tipologia_file',FORM='tipo_form')

La struttura di questo comando è fissata e consiste in un comando in cui sono indicati UNIT=... cioè l'unità di appartenenza, FILE=... nome del file di testo, STATUS=... che indica di che tipo di file si tratta, FORM=... indica il formato di spaziatura utilizzato.

es:

    open(UNIT=2, FILE='input.txt',STATUS='old',FORM='formatted')

Lo status è una stringa, ossia una serie di caratteri che può essere di tipo OLD, NEW o SCRATCH. OLD deve essere specificato se il file che si sta aprendo è un file di input quindi è necessario che il file di testo di input sia già esistente al momento della creazione, della compilazione e dell'esecuzione del programma fortran; NEW va usato se invece si vuole scrivere l'output in un file di testo che non esiste e che quindi sarà creato appositamente dallo stesso fortran; SCRATCH invece viene usato se il file di testo è solo temporaneo utile alla sola esecuzione del programma e che viene cancellato alla sua terminazione.

La formattazione FORM può essere di tipo FORMATTED o UNFORMATTED rispettivamente se si vuole o meno usare lo standard di formattazione di default ASCII per la spaziatura tra gli elementi contenuti nel file di testo e l'ordine di lettura degli stessi. Unformatted usa il codice binario.

Il write andrà a scrivere "scrivimi" sul file output.txt, mentre il read andrà a cercare la variabile "definiscimi" sul file input.txt.

Nell'apertura di un file possono essere omessi i label e la specifica di status e form se si desidera che sia implicito l'utilizzo di default.


es:

    open(1,'input.txt')


in questo caso lo status verrà determinato automaticamente e il file verrà preso come formatted di default.

   nota: Se dichiaro lo status di old e non esiste alcun file con quel nome il programma si blocca fornendo un errore. 
         Se dichiaro lo status di new ed esiste già un file con lo stesso nome il programma si blocca allo stesso modo.
         Per evitare questo problema si richiama la compilazione del programma aggiungendo
         &&rm 'file_dichiarato_new' ; ./a.out



Per quanto riguarda il FORM si può dire che entrambi vantano aspetti positivi e negativi. Lo standard di default FORMATTED, è semplice da leggere ed interpretare (es: file di testo leggibile dagli umani); gli svantaggi di quest'ultimo invece riguardano un'aspetto prevalentemente computazionale. FORMATTED ha una lettura di tipo sequenziale o "Accesso sequenziale": legge i termini scorrendoli a partire dal primo, trovando nei separatori gli elementi di distinguo fra l'uno e l'altro. Tale procedimento purtroppo verrebbe svolto anche qualora si necessitasse di leggere solo gli ultimi 2 valori, aumentando in tal modo il numero di operazioni eseguite rispetto a quelle minime richieste. Un altro problema di tipo computazionale consiste nella memoria occupata dai singoli elementi. Un singolo carattere infatti può contenere molte di più delle 14 cifre da noi utilizzate. Il resto dello spazio rimane quindi inutilmente allocato. Sebbene non immediatamente interpretabile (una sequenza di byte), perché rappresenta l'immagine su disco di ciò che è presente in memoria , il form UNFORMATTED invece vanta un "comportamento" più efficace per quanto concerne l'aspetto computazionale es: se per ogni numero che scrivo vado a stoccare 8 byte, per andare a leggere l'ottavo numero salto i primi 56 byte e leggo i successivi 8 ( posso così spostare la testina del disco sull'ottavo elemento, anziché scorrere i primi 7 per arrivarvi) . "Scrivendo" le informazioni così come sono stoccate in memoria, tale form ci permette di "puntare" alla loro specifica posizione. In questo modo si può "saltare" da una cifra all'altra senza difficoltà ed in maniera più immediata, perché è nota a priori la lunghezza delle cifre precedenti (ad esempio se ho dei numeri reali in singola precisione ognuno di essi occupa 4 byte). Tale comportamento è chiamato ad "Random Access" o "Accesso Casuale", tuttavia random non sta a significare una vera e propria selezione casuale, ma significa che posso puntare una qualsiasi slot (casella). Di default, la forma utilizzata è la FORMATTED.

Formatted:
↓→......↓→.......    -> scorrimento sequenziale
8................    -> sequenza di byte
UnFormatted:
       ↓      ↓      -> scorrimento random
8......5......2..    -> sequenza di byte


I file vanno sempre chiusi con il comando CLOSE prima del termine del programma fortran:

   close (UNIT=1)

E' importante che questi siano chiusi, perché è al momento del “close” che le informazioni vengono scritte.


E' inoltre buona abitudine far scrivere a video del compilatore, quindi usando un generico write(*,*), una stringa che attesti la corretta esecuzione del programma fortran nel momento in cui tutti gli input ed output vengano gestiti da file esterni e di conseguenza il compilatore non darebbe all'utente la possibilità di riconoscere se il programma è riuscito a svolgere correttamente il suo compito o meno


Nella lettura e scrittura dei file si deve far riferimento all'unità che sto leggendo o scrivendo indirizzandole sul file precedentemente aperto.

Per scrivere una stringa o un valore di una variabile si usa il write indirizzato sul file invece che allo schermo


   write(unit_da_scrivere,*) 'stringa da scrivere', variabile da scrivere 


allo stesso modo si esegue la lettura da un file con il read indirizzato sul file precedentemente aperto che desideriamo leggere.


   read(unit_da_leggere,*) variabili


Esempio svolto a lezione: Creiamo un file input.txt Il nostro file di input conterrà una matrice 5x3 (dimensione che dovrà essere esplicitata sulla prima riga del file input.txt, in quanto si ha bisogno di sapere in anticipo la memoria necessaria per l'allocazione dei dati) come riportato di seguito:

       5 3
       1.1   2.2   3.3
       4.4   5.5   6.6 
       1.2  -3.8   1.9 
       2.0   2.1   2.5
       3.4   4.5   5.6
c234567
      program iofile
      implicit none
     
      integer MMAX, NMAX, M, N, I, J
      parameter (MMAX=1000, NMAX=1000)
      double precision A(MMAX,NMAX)
c --- apro il file input e leggo la matrice:
      open(unit=2, FILE='input.txt', STATUS='old', FORM='FORMATTED')
c --- apro il file di output:
      open(unit=1, FILE='output.txt', STATUS='new', FORM='FORMATTED')
c --- leggo dal file di input il numero di righe e colonne della matrice: 
      read (UNIT=2,FMT=*) M, N
c --- echo delle lettura:
      write(UNIT=1,FMT=*) 'righe: ', M , 'colonne: ', N
c --- leggo dal file di input gli elementi della matrice, con echo:
      do I=1,M
       write (UNIT=1,FMT=*) 'leggo riga: ', I
       read (UNIT=2,FMT=*) (A(I,J) , J=1,N)
       write (UNIT=1,FMT=*) (A(I,J) , J=1,N)
      enddo
c --- chiudo il file dopo averlo letto:
      close (UNIT=2)
c --- chiudo il file dopo averlo letto:
      close (UNIT=1)
     
      write(*,*) 'Esecuzione corretta'
      stop
      end 


La lettura e la scrittura prevedono sempre un 'avanzamento'. Per “riavvolgere” il file ho 2 strade:

  • chiudo e riapro il file;
  • comando REWIND (es. Rewind 2) che riavvolge la posizione del puntatore all'inizio per rileggere tutto il file; in pratica troverò scritta due volte la matrice nel file output.txt.

C'è anche il comando BACKSPACE che serve a rileggere il carattere precedente, ovvero arretra il punto di lettura di un elemento (potrebbe essere utile nel caso in cui avessi un carattere 'termine riga').


Se nel mio file di input venisse a mancare un carattere che io chiedo, mi viene dato un runtime error: End of file (ovvero il file è troppo “corto”).

Problema di Fortran: non sa fare funzioni come calcolare gli autovalori ed altri tipi di operazioni matriciali, perciò per farlo dovrei scrivere molte righe di programma, per cui uso dei formati standard che si possono trovare tranquillamente su internet al seguente link:

lapack è una libreria condivisa che posso implementare su fortran per avere maggiore completezza. per richiamarla basta scrivere sul compilatore:

gfortran provalapack.for.llapack

UTILIZZO DI LIBRERIE ESTERNE

Esistono in rete delle librerie di riferimento in cui è possibile trovare alcuni programmi svolti. Le più famose sono:

  • LAPACK (Contiene operazioni matriciali, calcolo di rango, determinante ecc.)
  • BLAS (contenente operazioni matriciali)

LAPACK (Linear Algebra PACKage) è un insieme di librerie software usate per effettuare calcoli scientifici ad alto livello. Tali librerie sono state scritte in Fortran 77 e sono di dominio pubblico, anche se esistono delle personalizzazioni di queste librerie a pagamento. L'ultima versione è la 3.5.0 del 16 novembre 2013. Mediante queste librerie, è possibile effettuare calcoli di notevole importanza, come il calcolo di autovalori ed autovettori di matrici, risoluzione di sistemi lineari, fattorizzazioni di matrici, e molto altro ancora. Ad esempio, esistono delle procedure interne, nascoste all'utente, in grado di trasformare la matrice iniziale in una matrice tridiagonale, per poi calcolare effettivamente autovalori ed autovettori. Come dipendenza questa libreria richiede l'installazione di un'altra libreria: la BLAS (Basic Linear Algebra Subprograms). Le procedure all'interno di tale libreria consentono di effettuare calcoli scientifici di più basso livello, come il calcolo del prodotto scalare tra due vettori, prodotto matrice per vettore, etc.

BLAS (Basic Linear Algebra Subprograms) sono routine standard che forniscono elementi di base per l’esecuzione delle operazioni su vettori e matrici. Il Livello 1 di BLAS esegue operazioni scalari, vettoriali e vettore-vettore, il Livello 2 di BLAS esegue operazioni matrice-vettore, e il Livello 3 di BLAS risolve operazioni matrice-matrice. Poiché BLAS risulta particolarmente efficiente, portatile, e ampiamente disponibile, è comunemente usata per lo sviluppo di software di algebra lineare di alta qualità.


Primo approccio all'uso di librerie esterne. Si vuole provare ora ad utilizzare un esempio di codice scaricato da internet, presumibilmente funzionante.

Esempio: (provalapack.for)

c234567======
      Program Eigenvalue
c finding the eigenvalues of a complex matrix using LAPACK
      Implicit none
c declarations, notice double precision
       complex*16 A(3,3), b(3), DUMMY(1,1), WORK(6)
       integer i, ok
c define matrix A
      A(1,1)=(3.1, -1.8)
      A(1,2)=(1.3, 0.2) 
      A(1,3)=(-5.7, -4.3)
      A(2,1)=(1.0, 0)
      A(2,2)=(-6.9, 3.2)
      A(2,3)=(5.8, 2.2)
      A(3,1)=(3.4, -4.0)
      A(3,2)=(7.2, 2.9)
      A(3,3)=(-8.8, 3.2)
c
c find the solution using the LAPACK routine ZGEEV
c234567================================================================!
      call ZGEEV('N', 'N', 3, A, 3, b, DUMMY, 1, DUMMY, 1, WORK, 6, 
     $WORK,ok)
c
c parameters in the order as they appear in the function call
c    no left eigenvectors, no right eigenvectors, order of input matrix A,
c    input matrix A, leading dimension of A, array for eigenvalues, 
c    array for left eigenvalue, leading dimension of DUMMY, 
c    array for right eigenvalues, leading dimension of DUMMY,
c    workspace array dim>=2*order of A, dimension of WORK
c    workspace array dim=2*order of A, return value 
c
c output of eigenvalues
      if (ok .eq. 0) then
      do i=1, 3
      write(*,*) b(i)
      enddo
      else
      write (*,*) "An error occured"
      endif
      end

Primo tentativo (con risultato negativo)

In via teorica sarebbe sufficiente accodare al listato sopra riportato la definizione della subroutine ZGEEV(..) ivi chiamata.

Una rapida ricerca su google ci riporta al codice sorgente di tale subroutine, vedi qui. Purtroppo però la subroutine non è autocontenuta, e a sua volta richiama altre subroutine del pacchetto lapack, la definizione delle quali deve essere inclusa nel listato al fine di procedere con una corretta compilazione. A questo scopo viene fornita una versione della stessa con dipendenze incluse.

Procediamo accodando tale listato al programma di esempio. Risultato di un primo tentativo di compilazione è una lunga sequenza di chiamate a subroutine/funzioni non trovate

/tmp/ccDo6snr.o: In function `zgeev_':
provalapack.for:(.text+0xef9): undefined reference to `dznrm2_'
provalapack.for:(.text+0xf50): undefined reference to `zdscal_'
provalapack.for:(.text+0x1029): undefined reference to `idamax_'
provalapack.for:(.text+0x11fb): undefined reference to `zscal_'
[...]
provalapack.for:(.text+0x29d5c): undefined reference to `zgemm_'
provalapack.for:(.text+0x29dda): undefined reference to `ztrmm_'
collect2: ld returned 1 exit status

ed un errore al linker (ld). Le singole unità di codice vengono infatti compilate senza errori, ma il tentativo di unione delle stesse a definire un unico eseguibile autocontenuto fallisce.

L'elenco delle subroutine/funzioni mancanti risulta essere il seguente: dscal, dzasum, dznrm2, idamax, izamax, zaxpy, zcopy, zdotc, zdotu, zdscal, zgebak, zgebal, zgeev, zgehrd, zgemm, zgemv, zgerc, zhseqr, zlahqr, zlahr2, zlaqr2, zlaqr3, zlaqr5, zlarf, zlarfb, zlarfg, zlarft, zlatrs, zscal, zswap, ztrevc, ztrmm, ztrmv, ztrsv, zung2r. Tali subroutine potrebbero a loro volta dipendere da ulteriori subroutine.

Sono tutte subroutine della libreria blas, fornita insieme al pacchetto sorgenti lapack. Si procede quindi all'inclusione dell'intero blocco di codice relativo tale libreria entro il file provalapack.for ottenendo il file completo File:Provalapack blas.for.zip.

Tale file viene compilato correttamente con

gfortran provalapack.blas.for && ./a.out

producendo un eseguibile funzionante.

Metodo rapido ed efficace

Compilare il codice di cui sopra, previa installazione delle librerie lapack (presenti sulle macchine del laboratorio 'linfa'), con

gfortran provalapack.for -llapack && ./a.out

L'opzione

-lNOMELIBRERIA

indica che verranno utilizzate le funzioni precompilate della shared library NOMELIBRERIA.

INTRINSIC e EXTERNAL

Le funzioni possono essere definite come:

  • INTRINSIC (quando è una funzione appartenente al codice Fortran; ad esempio: sin, cos, abs,...ecc)
  • EXTERNAL (quando è una funzione esterna alla specifica unità di codice)


Riportiamo ora per completezza le liste di tali funzioni:

Lista delle funzioni INTRINSIC, fortran 77. Lista delle funzioni INTRINSIC, compilatore gfortran. Molte di queste sono proprie degli standard Fortran 90 e successivi.


Una funzione è dichiarata EXTERNAL qualora non INTRINSIC e definita in altre unità di codice.


Le dichiarazioni INTRINSIC e EXTERNAL, usualmente omesse, sono necessarie qualora si passi la funzione/subroutine come argomento ad un'altra funzione/subroutine.


Riferimenti Bibliografici

- LAPACK, Wikipedia, http://it.wikipedia.org/wiki/LAPACK

- Blas: libreria fortran, http://guide.supereva.it/fortran/interventi/2009/06/blas-libreria-fortran