Laboratorio di Calcolo. AA 2019/20.
Esercitazione n. 2

Scopo di questa esercitazione è di familiarizzarsi con le manipolazioni di base di files e directories, con l' interfaccia di linea, con il meccanismo di scrittura, compilazione ed esecuzione  di semplici programmi. Il primo programma risolve la generica equazione algebrica di secondo grado in ampo complesso. Successivamente dovrà esser modificato per lavorare in campo reale. Infine qualche esercizio di base su cicli e operazioni completeranno un primo giro delle caratteristiche di base del linguaggio.

Gli esercizi prima della linea orizzontale andrebbero implementati (o almeno ne andrebbe tentata l'implementazione) prima del turno di laboratorio. Quelli dopo la linea orizzontale sono esercizi da fare dopo i pomeriggio di laboratorio. Tutti gli esercizi potrano essere ri-discussi e approfonditi sul forum dedicato agli esercizi sul sito moodle.
  1. (Introduzione) La prima cosa da imparare a fare è l' operazione di "lanciare" un programma, ovvero richiederne l' esecuzione al sistema operativo attraverso l' interprete di comandi. Il funzionamento dell' interprete di comandi UNIX è  il seguente:  in genere l ' interprete di comandi segnala di essere in attesa di un comando  visualizzando sullo schermo una scritta (il prompt) dopo la quale appare qualsiasi carattere venga digitato sulla tastiera.
    Il prompt varia da sistema a sistema e da utente a utente (eventualmente è riconfigurabile dall' utente).
    Se digito qualcosa da tastiera e premo il tasto "invio" (o "enter" o  "return"  a seconda della testiera) l' interprete decomporrà in parole la sequenza di caratteri dell' input, utilizzando uno o più spazi eventualmente presenti come separatori. La prima parola così ottenuta sarà considerata un possibile nome di file contenente istruzioni macchina oppure istruzioni per l' interprete (N.B. solo per pochissimi comandi, i cosiddetti comandi di shell, non c'è  un file corrispondente al comando ma questo viene implementato direttamente da istruzioni macchina della stessa shell). Se il file viene indicato mediante il nome corto verrà  ricercato solo all' interno di un insieme di directory predefinito (il cosiddetto path). Posso sempre indicare in modo compatto il nome lungo di un file nella directory corrente premettendo ./ al suo nome corto.  Il punto davanti allo slash (./)  rappresenta la directory corrente. Per esempio, i file di istruzioni eseguibili creati dal compilatore, per essere eseguiti, in assenza di personalizzazioni di cui si parlerà più avanti nel corso, dovranno essere indicati nella linea di comando come:   ./a.out, ./a.exe, ./pippo o altro nome.

  2. Dall' interprete di comandi, verificare qual è  la directory corrente mediante il comando pwd.  Quella ad apertura di terminale è la cosiddetta "home directory" o "directory di login". Esaminare quali file sono contenuti nella directory corrente mediante il comando ls.  Provare adesso con il comando   ls  -a .

    Cosa hanno in comune i file che sono "apparsi" con l' opzione -a ?

    Creare una directory di nome es1 (il comando è  mkdir:  mkdir es1). Una volta creata la directory es1, si potrà far diventare questa la directory di default col comando   cd es1. In questo modo tutti i nomi "corti" dei files saranno completati dal sistema con la gerarchia di directory che termina in es1:  p.es     il file   pippo   sarà interpretato come corrispondente al nome lungo.
     

  3. Creare mediante l' editor (e un copia-incolla) un file di testo contenente il seguente programma Fortran che risolve l' equazione generica di secondo grado a coefficienti reali. Compilare ed eseguire il programma ./a.out ( o ./a.exe su sisemi Windows) con input diversi e verificare i risultati ottenuti. Attenzione:  le radici  vengono scritte nella forma (A,B) dove A e B sono la parte reale e quella immaginaria del numero complesso.
  4. program secondo_grado

           
          implicit none              
    ! l' istruzione precedente obbliga a definire esplicitamente il tipo di ogni variabile

         
          real    :: a,b,c            ! i coefficienti reali dell' equazione
                                      ! a x**2 + b x + c = 0

          complex :: discr,xp,xm      ! discriminante e soluzioni corrispondenti
                                      ! alle due scelte del segno davanti la
                                      ! radice quadrata

          read*, a,b,c                ! i coefficienti vengono letti da tastiera

          discr = b**2 - 4*a*c
          xp = ( -b + sqrt(discr) )/(2*a)
          xm = ( -b - sqrt(discr) )/(2*a)

          print*,xp,xm ! le due radici vengono scritte sullo schermo come complessi
    end program secondo_grado


     

    Quando si esegue il programma occorre ricordare che c'è un'istruzione read*,a,b,c che attende di ricevere 3 valori di tipo real, sulla base dei quali costruire il discriminante e da questo le soluzioni complesse. Finché i 3 valori non sono stati immessi, il programma resterà in attesa.

    Verificare in qualche caso semplice (p.es. con input

    1 0 0

    1 0 -1

    1 0 1

    1 -2 1

    che il programma dia i risultati attesi. Prender nota di eventuali risultati inattesi.

    Nel compilare i sorgenti Fortran può esser comodo utilizzare uno dei parametri opzionali del comando gfortran per attribuire al file binario eseguibile prodotto dal compilatore un nome diverso da a.out (risparmiando quindi l'  eventuale passo di cambiarne  successivamente  il nome con i comandi di shell). Il parametro è
    -o file
    dove file indica il nome che si vuole attribuire all' eseguibile. P.es. 
    gfortran   pippo.f90  -o pippo.x
    il significato del comando è di compilare il file pippo.f90 e di scrivere in output ( da cui -o ) il file binario (istruzioni macchina corrispondenti)  sul file il cui nome segue immediatamente il modificatore -o.
    Attenzione però a non sbagliarsi: se si mette per errore il nome del file sorgente ( .f90) subito dopo -o il file sorgente verrà cancellato!  
     

  5. Modificare il programma Fortran per la soluzione dell' equazione di secondo grado del precedente esercizio cambiando solo i tipi dati in modo che lavori con tutte le variabili di tipo real  (al posto di complex  scrivere real). Cosa succede se i coefficienti sono tali da dar luogo a discriminante negativo ? (Provare) Il valore NaN sta per "Not A Number" e segnala un'operazione illegale o non definita.
  6. Il costrutto
  7. IF( espressione logica )THEN
    ...blocco 1
    ELSE
    ...blocco 2
    END IF

    esegue le istruzioni del  blocco 1 se l' espressione logica tra parentesi è  vera e quelle del blocco 2 se è  falsa. Usare il costrutto Fortran  IF( ...) THEN ... ELSE ... END IF per fare in modo che il programma per la soluzione dell' equazione di secondo grado scriva le soluzioni reali solo se esistono, mentre,  se non esistono, visualizzi su schermo un messaggio di errore. (Ricordare che l' ordine con  cui si scrivono le istruzioni nel sorgente Fortran corrisponde a quello di esecuzione delle corrispondenti istruzioni macchina.)

  8. Si può  attribuire un valore iniziale ad una variabile nella stessa istruzione in cui se ne   definisce il  tipo. Le variabili possono poi essere ridefinite nel programma  assegnando loro un nuovo valore (eventualmente dipendente dal vecchio).
    Es:
     

        program par
    implicit none
    integer :: i=2

    print*,i
    i=i+1

    print*,i, i+3
    print*,i
    end program par

     
     

    verificare il funzionamento del programma compilandolo ed eseguendolo.

  9.  
  10. Che valore della variabile  somma  scriverà  il seguente frammento di codice ? (per avere un programma compilabile occorrerà aggiungere le istruzioni   program ... , end program ...,  implicit none nell' ordine giusto.  Si ricorda che l' istruzione  do j=1,10 fa ripetere per 10 volte le istruzioni tra    do   e end do  )
  11. integer :: somma , j

    somma = 0
    do j = 1,10

       somma = somma + j
    end do
    print*,somma
     


  12. Scrivere un programma  che dia  il risultato della somma dei quadrati dei primi N interi. (Il programma deve leggere il valore di N da tastiera, sommare uno ad uno gli N termini e confrontare il risultato con quello ottenuto mediante l' espressione analitica per la somma:
    N(N+1)(2N+1)/6 ).  Utilizzare quanto appreso nel precedente esercizio.

    Dopo aver verificato che il calcolo della somma attraverso il ciclo do e la formula analitica diano lo stesso valore per N piccoli, controllare il funzionamento del programma per valori grandi di N (quanto grandi?).


  13. Visualizzare il grafico della funzione e-4 x2 sin(10 x) nell' intervallo [-1.5,1.5] valutando e scrivendo su  file coppie di numeri costituite da ascisse e valori della funzione su punti equispaziati nell' intervallo considerato.

    Suggerimenti: procedere per passi successivi secondo la seguente successione.

    1. Scrivere un programma che scriva su schermo le ascisse dopo aver letto il numero di intervalini N in cui si vuol suddividere l' intervallo dato.  Da ricordare:
    a) gli indici dei DO devono essere variabili di tipo INTEGER
    b) si possono mettere in corrispondenza le ascisse (reali) equispaziate nell' intervallo [a,b] con gli interi da 0 a N attraverso la relazione lineare: xi=a + i/N*(b-a)  

    (ATTENZIONE però  a come si implementa in Fortran la precedente formula matematica!)

    2. aggiungere  il calcolo e la scrittura su schermo delle ordinate (i valori della funzione). Sullo schermo dovranno apparire su ogni riga una coppia costituita da ascissa e ordinata.

     

    3. modificare infine l' istruzione di scrittura delle coppie in modo che vengano scritte su  file fort.1.
    Per scrivere su file, invece dell' istruzione print*,... si può usare l' istruzione write(unit=M,fmt=*)... dove ... rappresenta la lista delle variabili o costanti che si vuole scrivere. M rappresenta un intero positivo a cui deve essere assegnato un valore intero (con gfortran  deve essere diverso da 0, 5 e 6). Il file su cui il programma scriverà avrà nome fort.M (esempio: write(unit=10,fmt=*)a,b scriverà i valori delle variabili a e b sul file fort.10; è anche lecito abbreviare l' istruzione come: write(10,*)a,b.  
  14.     4. Per visualizzare un grafico x-y: dalla finestra terminale dare il comando
                 gnuplot
                e poi, al prompt (gnuplot>), il comando

                 plot    'fort.1'    with   lines     (che si puo' abbreviare in     p  'fort.1'   w l )

              per avere un grafico del risultato. 

             Se si avessero più colonne nello stesso file e si volessero  graficare i dati delle colonne 2 e 4 il comando andrebbe  modificato come:

              plot   'fort.1'   using 2:4 w l

               abbreviabile in:

             p   'fort.1'   u 2:4 w l

     Per terminare dare quit; per avere aiuto circa l' uso di gnuplot: help. Un'  osservazione direta del  grafico risultante dovrebbe darci qualche indicazione sull'  adeguatezza del campionamento della  funzione.
          I grafici possono anche essere  salvati su file mediante i comandi di gnuplot:

          set term pdf

    set output "nomefile.pdf"
                seguiti dalla lista di comandi per tracciare i grafici oppure  replot  (cfr help gnuplot per l' utilizzo).
    Infine occorre ripristinare il term originale (in aula Poropat wxt):
    set term wxt
    oppure uscire da gnuplot.
           I file pdf  visualizzati  con applicazioni quali xpdf :        xpdf nomefile.pdf.

     




    Gli esercizi successivi possono essere completati  individualmente nei giorni successivi, prima della prossima esercitazione. Possibili soluzioni (ce n'è  più  di una) problemi, commenti, possono essere utilmente mandati sul Forum. Gli esercizi 8 e 9 vanno considerati obbligatori.


    1. Prevedere  cosa dovrebbe scrivere il seguente programma  scrivendo le proprie previsioni  su un foglio, trascrivere il programma su di un file e compilarlo. Eseguire il programma verificando se  le previsioni fatte  erano corrette.

      Si ricorda che la sintassi di un DO i=e1,e2,e3 ... END DO (con es3 con valore diverso da 0), è tale che le istruzioni tra DO ed END DO sono eseguite  m=(e2-e1+e3)/e3 volte, se m è   un intero positivo e nessuna volta se m<=0,  partendo dal valore di e1 ed  incrementandolo del valore e3 ogni volta (che corrisponde ad un decremento se e3 è   negativo.
      Ricordare inoltre che se e3  è   assente,  si sottintende e3=1.
      Tenendo presente che il valore di e1,e2, ed e3 viene valutato una sola volta (quando il controllo incontra la linea DO per la prima volta e quindi nessun eventuale cambiamento dei valori di e1, e2 ed e3 all' interno del ciclo puo' modificare il numero di ripetizioni), si può anche riesprimere (in modo meno preciso) la condizione di esecuzione del ciclo dicendo che esso viene eseguito fintanto che vale la condizione i*e3<=e2*e3.

      Si ricordi anche che tutto il contenuto di una linea dal carattere !  in poi viene ignorato dal compilatore (  costituisce un commento al codice ).

    2.  

      program cicli_do
      implicit none
      integer :: i,j,n_ciclo = 1    ! la variabile n_ciclo e' inizializzata a 1 oltre che dichiarata

      print*," ciclo N.   ",n_ciclo     ! ciclo n. 1 ; scriviamo di quale
                                        ! ciclo si tratta per identificarlo in output
      do i = 1,10,1
         print*,i
      end do

      n_ciclo = n_ciclo + 1             ! incrementiamo il numero di ciclo
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 2
      do i = 1,10
         print*,i
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 3
      do i = 1,10,-1
         print*,i
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 4
      do i = 10,10,2
         print*,i
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 5
      do i = 10,1,-1
         print*,i
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 6
      do i = -10,10,3
         print*,i
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 7

      do i = 1,3
         do j = 1,3
            print*,"i= ",i," j= ",j
         end do
      end do

      n_ciclo = n_ciclo + 1
      print*," ciclo N.   ",n_ciclo     ! ciclo n. 8
      do i = 1,3
         do j = i+1,3
            print*,"i= ",i," j= ",j
         end do
      end do
      end program cicli_do



    3. Creare mediante l' editor un file di testo contenente il seguente programma Fortran. Provare a prevedere il risultato delle varie espressioni presenti,  PRIMA di eseguirlo, scrivendo le proprie previsioni  su un foglio  e poi verificarle (ricordare le regole per l' aritmetica dei diversi tipi dati numerici).  Vanno tenute presenti le regole di conversione dei tipi dati in espressioni miste e la conversione implicita che avviene quando si assegna un valore di un tipo dati da una variabile di altro tipo dati.

    4. Nota: l'istruzione implicit none che appare come prima istruzione nei programmi equivale a richiedere che siano date le dichiarazioni esplicite del tipo dati di ogni variabile utilizzata nel programma. In assenza di tale istruzione, il Fortran assumerebbe che le variabili non dichiarate esplicitamente il cui nome inizi per lettere comprese tra a e h e tra o e z siano reali mentre quelle il cui nome inizia per i,j,k,l,m,n siano intere. Questa convenzione implicita del Fortran  è   potenzialmente pericolosa per la possibilita  di introdurre inavvertitamente variabili non previste (e  non inizializzate) e quindi  è  buona norma utilizzare sempre l' istruzione implict none.
       
        

      program espres
      implicit none
      real :: ra,rb,rc,rd,re,rf,rg,rh,ri,rs
      integer :: ip,iq,ir,is,iu,iw,ix,iy,ij
      logical :: les1,les2,les3

      ra = 2
      rb = 1
      rc = 3.0
      ip = 2
      iq = 3
      rd = rb/ra + rc
      re = rb/rc + ra
      rf = ip/iq
      rg = ip/iq*iq/ip
      ir = ra/rc*rc/ra
      iu = (ra/rc)*(rc/ra)
      iw = 8**(1/3)
      rh = 4.7
      ri = 10.2
      ix = rh
      iy = ri
      ij = -4.7
      les1 = ij < -4.0
      les2 = .true. .and. (3*(1/3) == 1)
      ! il risultato di un' operazione di AND logico
      ! e' vero se e solo se sono veri tutti e due gli operandi

      les3 = (1<2) .or. (3<=3) .and. (-5>-4.0) ! chi ha precedenza tra un .or. e un .and. ?

      print *," in questo programma nomi che iniziano per r indicano variabili reali"
      print *," nomi che iniziano per i indicano variabili intere, con l logiche"
      print*," ra = 2: ra = ", ra
      print*," rb = 1: rb = ", rb
      print*," rc = 3.0: rc = ", rc
      print*," ip = 2: ip = ", ip
      print*," iq = 3: iq = ", iq

      print *," rd = rb/ra + rc = ",rd
      print *," re = rb/rc + ra = ",re
      print *," rf = ip/iq = ",rf
      print *," rg = ip/iq*iq/ip = ",rg
      print *," ir = ra/rc*rc/ra = ",ir
      print *," iu = (ra/rc)*(rc/ra) = ",iu
      print *," is = ",is , " rs = ", rs, &
      " (variabili dichiarate ma non definite nel programma) "
      print *," iw = 8**(1/3) = ",iw
      print *," ix = rh: ix = ",ix
      print *," iy = ri: iy = ",iy
      print *," ij = -4.7: ij = ",ij
      print *,"2**3**2 = ", 2**3**2," quale potenza viene calcolata per prima ?"

      print *,"les1 = ij < -4.0 : = ", les1
      print *,"les2 = .true. .and. (3*(1/3) == 1) : = ",les2
      print *,"les3 = (1<2) .or. (3<=3) .and. (-5>-4.0) : = ",les3

      end program espres

    5. Scrivere un programma che mostri su schermo i quadrati, i cubi e le radici quadrate degli interi da 1 a 10 (Ricordare che c'è la funzione sqrt(x) per calcolare la radice quadrata di x. Tuttavia la funzione sqrt  richiede che l' argomento sia real. Pertanto occorrerà   trasformare un intero in reale.  Questo  può  esser fatto  mediante la funzione real(x), utilizzando una variabile reale intermedia o  anche moltiplicando l'  intero per 1.0 nell' argomento della funzione sqrt (perché ?))

    6. Scrivere un programma che generi e scriva i primi 100 interi ma, per quelli che sono quadrati perfetti (cioe' della forma N*N) scriva accanto al numero la stringa "Quadrato  perfetto ". Suggerimento: cercare metodi semplici per ottenere i quadrati perfetti.  Prima di scrivere anche una sola riga di codice,  cercate di scrivere nella forma più  chiara possibile un algoritmo che risolva il problema.


    7. Modificare il programma precedente in modo che scriva i risultati su un file di nome fort.10.

    8. Scrivere un programma che calcoli le somme parziali (a partire da n=1) della successione di termine generale (-1)n-1xn/n . La successione delle somme parziali (serie), quando converge, ha come limite la funzione log(1+x). La funzione logaritmo è disponibile in un programma Fortran attraverso la funzione log(x). Verificare quanti termini occorrono per calcolare log(0.2), log(2) e log(20) con un errore relativo minore di 10-4. Suggerimenti: calcolare l' errore relativo come valore assoluto della differenza tra valore approssimato e valore esatto diviso per il valore esatto. Il valore assoluto di un' espressione x è  dato dalla funzione intrinseca ABS(x). Ricordare inoltre che non necessariamente una serie deve convergere.





    Si consiglia, per prender pratica con l' interfaccia di linea unix (shell) di dedicare un po' di tempo a seguire interativamente il tutorial indicato alla fine di questa pagina.


    Per chi volesse familiarizzarsi rapidamente con i principali comandi della shell (l'interpretedi comandi), suggerisco di dedicare un po' di tempo a seguire (davanti ad un computer)  il tutorial UNIX Tutorial for Beginner, di  M.Stonebank@surrey.ac.uk (URL: http://www.ee.surrey.ac.uk/Teaching/Unix/ ). Per chi preferisse una guida in italiano è disponibile una traduzione in italiano delllo stesso tutorial, sebbene corrispondente ad una precedente versione (meno completa), figura tra i primi link nella pagina principale di questo  corso.