Questo sito contribuisce alla audience di

Introduzione al linguaggio F

Una evoluzione del linguaggio Fortran 90

Il Fortran il cui nome deriva dal
termine FORmula TRANslation è stato introdotto nel 1957 e rimane il
linguaggio preferito da molti programmatori scientifici. Il nuovo fortran
detto Fortran 90, è stato realizzato con una struttura simile al
linguaggio C. Le novità essenziali del Fortran 90 sono: subroutines
ricorsive, allocazione dinamica, puntatori, dati strutturati definiti
dall’utente, moduli, e la notevole abilità di manipolare interi array.

Il Fortran 90 è totalmente compatibile
con il Fortran 77 questo implica che sia ancora presente in esso della
sintassi considerata ormai obsoleta. Il linguaggio F invece è
una evoluzione del linguaggio Fortran 90 infatti include solo le
caratteristiche moderne è compatto, e facile da imparare.


Introduzione

Di seguito si supporrà
che il lettore abbia una certa familiarità con i concetti di base della
programmazione, inizialmente forniamo un esempio di un programma F.

program prodotto
real :: m, a, force
m = 2.0 ! massa in Kg
a = 4.0 ! accelerazione in unità mks
force = m*a ! forza in Newton
print *, force
end program prodotto

Le novità del linguaggio F possono
essere riassunte nei seguenti punti:

Il primo statement deve essere lo
statement program; L’ultimo statement deve essere il
corrispondente statement end program.

Le variabili numeriche di tipo intero
e quelle floating point sono diverse. Il nome di tutte le variabili
deve essere costituito da 1 a 31 caratteriri alfanumerici dei quali il
primo deve essere una lettera e l’ultimo non deve essere un
underscore.

Il tipo di ogni variabile deve essere
dichiarato esplicitamente.

Il numero reale due deve essere
scritto come 2.0 e non come 2.

Il linguaggio F è case sensitive ma
due nomi che differiscono solo dal fatto di avere una o più lettere
maiuscola o minuscola rispettivamente non possono essere utilizzate.
Tutte le keywords (parole chiave del linguaggio che quindi non
possono essere ridefinite) son scritte in minuscolo. Molti nome come
ad esempio product sono riservati e non possono essere
utilizzati come nomi.

I commentio iniziano con il simbolo !
e possono essere ovunque nel programma.

Gli statements su linee che possono
contenere al più 132 caratteri.

Se si pone un asterisco (*) dopo il
comando print si sceglie il formato di default.

Di seguito introduciamo
la sintassi necessarie che ci permette di associare alle variabili m
e a il valore da noi desiderato ed introdotto da tastiera. Da
notare l’uso dello statement read non formattato.


program prodotto2
real :: m, a, force
! SI units
print *, “mass m = ?”
read *, m
print *, “acceleration a = ?”
read *, a
force = m*a
print *, “force (in newtons) =”, force
end program prodotto2


Il Costrutto Do

Il linguaggio F utilizza il costrutto do
per fare in modo che il computer esegua gli stessi statements più di una
volta. Un esempio di un costrutto do è di seguito riportato:


program serie
integer :: n
real :: sum_serie ! sum è una keyword
sum_serie = 0.0
! aggiunge i primi 100 termini di una serie semplice
do n = 1, 100
sum_serie = sum_serie + 1.0/real(n)**2
print *, n,sum_serie
end do
end program serie

Da notare che n
è una variabile integer. In questo caso lo statement do
specifica il primo e l’ultimo valore di n; n si incrementa di una
unità per defaultì. Il blocco degli statements interni al loop si
indenta per chiarezza.

Poiché il prodotto n*n
è ottenuto utilizzando un’aritmetica intera, iè meglio convertire n
in variabile reale prima della moltiplicazione. Inoltre si può notare che
l’esponente è ottenuto utilizzando l’operatore **.


Il costrutto IF

Nel prossimo esempio l’uscita dal loop
Do è vincolata dal soddisfacimento di un test.


program serie_test
! Illustrazione dell’uso del costrutto do
integer :: n
real :: sum_series, newterm, relative_change
n = 0
sum_series = 0.0
do
n = n + 1
newterm = 1.0/(n*n)
sum_series = sum_series + newterm
relative_change = newterm/sum_series
if (relative_change < 0.0001) then
exit
end if
print *, n,relative_change,sum_series
end do
end program serie_test

Le novità introdotte dal linguaggio F
presenti nell’esempio precedente sono:

L’uscita dal costrutto do è
realizzata con l’utilizzo dello statement exit.
Il costrutto if determina
l’esecuzione di una sequenza di statements (un blocco) che dipende
da una condizione. Il costrutto if è un compound statement
ed inizia con ifthen e termina con end if.
Il blocco interno al costrutto if è indentato per chiarezza.

Tabella 1. Sommario degli
operatori relazionali.

relazione


Operatore

Minore

<

Minore uguale

<=

Uguale

==

Non uguale

/=

Maggiore

>

Maqgiore uguale

>=

Il programma seguente illustra l’uso del parametro kind e del
costrutto do con nome:


program series_example
! illustra l’uso del parametro kind e del loop do con nome
integer, parameter :: double = 8
integer :: n
real (kind = double) :: sum_series, newterm, relative_change
n = 0
sum_series = 0.0
print_change: do
n = n + 1
newterm = 1.0/real(n, kind = double)**2
sum_series = sum_series + newterm
relative_change = newterm/sum_series
if (relative_change < 0.0001) then
exit print_change
end if
print *, n,relative_change,sum_series
end do print_change
end program series_example

Le variabili possono avere una
rappresentazione diversa a seconda dell’ hardware utilizzato, la
rappresentazione in doppia precisione può essere ottenuta utilizzando
kind = double in parentesi dopo la keyword real.

La rappresentazione in doppia
precisione è ottenuta in Fortran 90 su macchine SGI e in F su
macchine Macintosh ponendo:

double = 8

Inoltre la doppia precisione in F su
macchine SGI è ottenuta ponendo:

double = 2

Gli statements do e end do
devono avere lo stesso nome oppure devono non averlo entrambi. In
generale, il costrutto do è nominato esplicitamente quando è
inserito in un ciclo innestato. L’uso di un costrutto do con
nome nell’esempio precedente è superfluo ed è stato inserito solo
per illustrare tale tecnica.

I Subprograms sono
chiamati dal main program o da altri subprograms. Ad esempio, nel
programma seguente si aggiunge e si moltiplica due numeri introdotti da
tastiera. Da notare che le variabili x e y sono public
e sono disponibile dal main program.

module common
public :: initial,add,multiply
integer, parameter, public :: double = 8
real (kind = double), public :: x,y

contains

subroutine initial()
print *, “x = ?”
read *,x
print *, “y = ?”
read *,y
end subroutine initial

subroutine add(sum2)
real (kind = double), intent (in out) :: sum2
sum2 = x + y
end subroutine add

subroutine multiply(product2)
real (kind = double), intent (in out) :: product2
product2 = x*y
end subroutine multiply

end module common

program tasks ! illustra l’uso dei module e delle subroutines
! notare quante variabikli sono passate
use common

real (kind = double) :: sum2, product2
call initial() ! initializza le variabili
call add(sum2) ! aggiunge due variabili
call multiply(product2)
print *, “sum =”, sum2, “product =”, product2
end program tasks

I Subprograms (subroutines e
functions) sono contenuti nei moduli. La forma dei module, subroutine,
e function è simile al main program.

Un module è richiamato nel main
program con l’utilizzo dello statement use.

Le Subroutines sono chiamate nel
main program utilizzando lo statement call.

Un subprogram è sempre
richiamabile da altri segmenti presenti nel module.

I subprograms nei module sono
preceduti dallo statement contains.

Variabili e subprograms possono
essere dichiarati come public in un module e possono essere
richiamabili dal main program (e da altri moduli).

Inoltre informazioni possono essere
passate come argomenti ad ogni altro subprogram, esempi sono le
variabili sum2 e product2 del programma precedente. Una parentesi
tonda () è sempre necessaria se non ci sono argomenti. Ogni
argomento locale del programma deve essere accompagnato dal suo intent.

intent in sta a significare
che l’argomento dummy non può variare nel subprogram.

intent out sta a significare
che l’argomento dummy non può essere usato fino a quando il suo
valore non viene calcolato nel subprogram e passato al programma
chiamante.

intent in out sta a
significare che l’argomento dummy ha un suo valore iniziale, che
cambia nel subprogram, ed il suo valore viene poi passato al programma
chiamante.

I moduli possono essere contenuti in
file separati dal programma principale.


Output formattato

Cerchiamo ora di
anlizzare nel dettaglio quali direttive impartire per ottenere un output
formattato. Abbiamo detto che l’asterisco * denota il formato di default,
per ottenere invece un fortato specifico bisogna associare al comando di
output una lista di descrittori di formato. Un esempio di direttiva di
output formattato è la seguente:

print “(t7,a,t16,a,t28,a)”, “tempo”,”T_caffè”,”T_caffè - T_stanza”

Il descrittore t
(tab) è usato per saltare ad una specifica posizione di una linea di
output. Il descrittore a (alfanumerico) è usato per le stringhe di
caratteri. Un esempio di descrittore f (floating point) è dato da:


print “(f10.2,2f13.4)”,t,T_caffè,T_caffè - T_stanza

Il descrittore f13.4
sta ad indicare che delle prossime 13 posizioni utilizzate per stampare un
valore reale, 4 posti sono riservati per stampare le cifre decimali dopo
la virgola. (Il punto decimale ed il segno meno occupano 2 posizioni delle
13 anzidette.) Il descrittore 2f13.4 sta ad indicare che il
descrittore f13.4 è utilizzato 2 volte. Un altro descrittore è i
(integer).

Un’altra novità nella sintassi del linguaggio F è nell’uso dello
statement parameter:


real (kind = double), public, parameter :: g = 9.8

Un parameter è una costante con
nome. Il suo valore è fissato atraverso la sua dichiarazione e non può
essere modificato durante l’esecuzione del programma.


Files

Il programma seguente mostra come aprire un nuovo file, scriverci
all’interno, chiudere lostesso ed infine come leggere dei dati da un
file già esistente:


program save_data
! illustra come scrivere e leggere su file
integer :: i,j,x
character(len = 32) :: file_name
print *, “name of file?”
read *, file_name
open (unit=5,file=file_name,action=”write”,status=”new”)
do i = 1,4
x = i*i
write (unit=5,fmt=*) i,x
end do
close(unit=5)
open (unit=1,file=file_name,action=”read”,status=”old”)
do i = 1,4
read (unit=1,fmt = *) j,x
print *, j,x
end do
close(unit=1)
end program save_data

Gli statements
Input/output si riferiscono a particolari file attraverso l’impiego
delle cosiddette unità (unit). Gli statements read e write
non si riferiscono direttamente ad un file, ma si riferiscono ad un numero
di un file che deve essere connesso al file in questione. Ci sono molti
modi di utilizzare lo statement open, ma l’esempio precedente è
tipico. Tra le parentesi tonde bisogna specificare l’unità alla quale
associare il file, il nome del file, il valore dello specificatore action
che può essere read, write, e readwrite (di default) ed infine status
che può assumere i seguenti valori old, new, replace, oppure scratch.

Se si suppone di
riutilizzare i dati ottenuti sullo stesso sistema su cui è stato
compilato il programma si possono usare operazioni di input/output non
formattate per salvare overhead, extra space, e roundoff error associati
alla conversione dei valori tra rappresentazione interna ed esterna.
Naturalmante l’uso di dati non formattati è strettamente dipendente
dalla macchina e dal compilatore utilizzati. L’accesso non formattato è
molto utile quando i dato sono generati da un programma e quindi
analizzati da un altro programma residenti entrambi sulla stessa macchina.
Per generare file non formattati basta omettere lo specificatore di
formato.


Arrays

Le definizioni e l’uso degli array sono indicate nel programma seguente:


module common
public :: initial,cross

contains

subroutine initial(a,b)
real, dimension (:), intent(out) :: a,b
a(1:3) = (/ 2.0, -3.0, -4.0 /)
b(1:3) = (/ 6.0, 5.0, 1.0 /)
end subroutine initial

subroutine cross(r,s)
real, dimension (:), intent(in) :: r,s
real, dimension (3) :: cross_product
! note use of dummy variables
integer :: component,i,j
do component = 1,3
i = modulo(component,3) + 1
j = modulo(i,3) + 1
cross_product(component) = r(i)*s(j) - s(i)*r(j)
end do
print *, “”
print *, “three components of the vector product:”
print “(a,t10,a,t16,a)”, “x”,”y”,”z”
print *, cross_product
end subroutine cross

end module common

program vector ! illustra l’uso degli array
use common
real, dimension (3) :: a,b
real :: dot
call initial(a,b)
dot = dot_product(a,b)
print *, “dot product = “, dot
call cross(a,b)
end program vector

Le novità nell’uso degli arrays sono:

Un array è dichiarato nella sezione
dichiarativa del programma, del module, oppure delle procedure
utilizzando l’attributo dimension. Degli esempi sono:

real, dimension (10) :: x,y

real, dimension (1:10) :: x,y

integer, dimension (-10:10) :: prob

integer, dimension (10,10) :: spin

Il numero degli elementi di un array
è indicato tra parentesi, in particolare il minimo valore di default
è 1. Per questa ragione i primi due statement precedenti sono
equivalenti.

Il minimo valore può essere un numero negativo.

Nell’esempio che segue è proposta
la dichiarazione di un array bidimensionale.

Per assegnare ad ogni elemento
dell’array un valore esplicito si può usare un costruttore di array
che è costituito da una lista unidimensionale di valori separati da
una virgola e delimetati da “(/” e “/)”. Un
esempio è:


a(1:3) = (/ 2.0, -3.0, -4.0 /)

che è equivalente alle tre istruzioni
seguenti:


a(1) = 2.0
a(2) = -3.0
a(3) = -4.0

Da notare che per stampare un array
basta digitare la seguente riga:

print *, cross_product

Il linguaggio F ha molte funzioni per la
moltiplicazione di array e matrici. Per esempio, la funzione dot_function
che opera con due vettori e fornisce il prodotto scalare. Alcune funzioni
per la manipolazione di array sono: maxval, minval, product,
e sum.

Allocate statement

Una delle più importanti novità introdotte con il Fortran 90 è
l’allocazione dinamica, attraverso la quale la grandezza degli array può
essere modificata durante l’esecuzione del programma. L’uso degli
statement allocate e deallocate è di seguito illustrata.
E’ da notare l’uso del ciclo implied do.

program dynamic_array
! semplice esempio di array dinamico
real, dimension (:), allocatable :: x
integer :: i,N

N = 2
allocate(x(N:2*N))
! ciclo implied do
x(N:2*N) = (/ (i*i, i = N, 2*N) /)
print *, x
deallocate(x)
allocate(x(N:3*N))
x = (/ (i*i, i = N, 3*N) /)
print *, x
end program dynamic_array


Un esempio del modo in cui è possibile
passare gli array è il seguente:


module param

integer, public, parameter :: double = 8

end module param

module common

use param
private

public :: initial

integer, public :: N

contains

subroutine initial(x)
real (kind = double), intent(inout), dimension(:) :: x
N = 100
x(1) = 1.0
end subroutine initial

end module common

program test
use param
use common
real (kind = double), allocatable,dimension (:) :: x
N = 10
allocate(x(N))
call initial(x)
end program test


Random number sequences

Il Fortran 90 include diverse procedure di costruzione che si rivelano
molto utili. Una delle più utili è la subroutine random_number.
Sebbene sia una buona idea scrivere una routine per la generazione casuale
di numeri utilizzando un algoritmo già testato su un particolare problema
di interesse, è d’altra parte conveniente utilizzare la subroutine
random_number nella fase di debugging o nel caso l’accuratezza dei dati
non sia così importante. Il programma che segue illustra diversi usi
della subroutine random_number e random_seed. E’ da notare
che gli argomenti rnd della random_number devono necessariamente essere
real, con intent out, e possono essere scalari oppure degli array.


program random_example
real :: rnd
real, dimension (:), allocatable :: x
integer, dimension(2) :: seed, seed_old
integer :: L,i,n_min,n_max,ran_int,sizer
! genera interi random tra n_min e n_max
! la dimensione di seed è 1 in F e 2 in Fortran 90.
call random_seed(sizer)
print *, sizer
! illustra l’uso di put e get
seed(1) = 1239
seed(2) = 9863 ! necessaria in Fortran 90
call random_seed(put=seed)
call random_seed(get=seed_old)
! conferma del valore di seed
print *, “seed = “, seed_old
L = 100 ! length of sequence
n_min = 1
n_max = 10
do i = 1,L
call random_number(rnd)
ran_int = (n_max - n_min + 1)*rnd + n_min
print *,ran_int
end do
! assegna i numeri random all’array x
allocate(x(L))
call random_number(x)
print “(4f13.6)”, x
call random_seed(get=seed_old)
print *, “seed = “, seed_old
end program random_example

E’ da notare come la subroutine
random_seed sia utilizzata per specificare seed. La specificazione è
utile quando la stessa sequenza di numeri random è utilizzata per testare
il programma.


Ricorsività

Un semplice esempio di definizione ricorsiva è fornito dalla funzione
fattoriale:

factorial(n) = n! = n(n-1)(n-2) … 1


La definizione ricorsiva del fattoriale
è:

factorial(1) = 1 factorial(n) = n
factorial(n-1)


Vediamo come è possibile introdurre
tale definizione in un programma:


module fact

public :: f
contains

recursive function f(n) result (factorial_result)
integer, intent (in) :: n
integer :: factorial_result

if (n <= 0) then
factorial_result = 1
else
factorial_result = n*f(n-1)
end if
end function f

end module fact

program test_factorial
use fact

integer :: n
print *, "integer n?"
read *, n
print "(i4, a, i10)", n, "! = ", f(n)
end program test_factorial

Nel programma greatest che segue,
(ricavato dal The Fun of Computing,John G. Kemeny, True BASIC
(1990)) dati due interi, n e m, si ricava il massimo comun divisore. Ad
esempio, se n = 1000 e m = 32, allora il massimo comun divisore (gcd) è
gcd = 8.

Un metodo per la ricerca del gcd è
quello di dividere n per m. Si scrive n = q m + r, dove q è il quoziente
e r è il resto. Se r = 0, allora m divide n e m è il gcd. Altrimenti, un
qualsiasi divisore tra m ed r divide anche n, allora risulta gcd(n,m) =
gcd(m,r). Poichè r < m, abbiamo reso la ricerca più facile. Ad
esempio, sia n = 1024 e m = 24. Allora q = 42 e r = 16. La nostra ricerca
si riduce dunque a gcd(24,16). Essendo q = 1 e r = 8 si calcola il gcd(16,8).
In definitiva q = 2, e r = 0 cosicchè gcd = 8. L’esempio seguente
implementa tale algoritmo.


module gcd_def

public :: gcd
contains

recursive function gcd(n,m) result (gcd_result)
integer, intent (in) :: n,m
integer :: gcd_result
integer :: remainder

remainder = modulo(n,m)
if (remainder == 0) then
gcd_result = m
else
gcd_result = gcd(m,remainder)
end if
end function gcd

end module gcd_def

program greatest
use gcd_def

integer :: n,m
print *, “enter two integers n, m”
read *, n,m
print “(a,i6,a,i6,a ,i6)”, “gcd of”,n,” and”,m,”=”,gcd(n,m)
end program greatest

Un ulteriore esempio di ricorsività è
fornito dalla cosiddetta torre di Hanoi.

Il volume di una ipersfera
d-dimensionale per unità di raggio può essere relazionato all’area di
una ipersfera (d - 1)-dimensionale. Il programma seguente utilizza una
subroutine ricorsiva per sfruttare tale proprietà e calcolare quindi il
volume per unità di area di una ipersfera d-dimensionale:


module common
public :: initialize,integrate

integer, parameter, public :: double = 8
real (kind = double), parameter, public :: zero = 0.0
real (kind = double), public :: h, volume
integer, public :: d

contains

subroutine initialize()
print *, “dimension d?”
read *, d ! spatial dimension
print *, “integration interval h?”
read *, h
volume = 0.0
end subroutine initialize

recursive subroutine integrate(lower_r2, remaining_d)
! lower_r2 is contribution to r^2 from lower dimensions
real(kind = double),intent (in) :: lower_r2
integer, intent (in) :: remaining_d ! # dimensions to integrate
real (kind = double) :: x

x = 0.5*h ! mid-point approximation
if (remaining_d > 1) then
lower_d: do
call integrate(lower_r2 + x**2, remaining_d - 1)
x = x + h
if (x > 1) then
exit lower_d
end if
end do lower_d
else
last_d: do
if (x**2 + lower_r2 <= 1) then
volume = volume + h**(d - 1)*(1 - lower_r2 - x**2)**0.5
end if
x = x + h
if (x > 1) then
exit last_d
end if
end do last_d
end if
end subroutine integrate

end module common

program hypersphere
! programma originale di Jon Goldstein
use common

call initialize()
call integrate(zero, d - 1)
volume = (2**d)*volume
print *, volume
end program hypersphere


Variabili di tipo carattere

L’unico operatiore intinseco per le espressioni contenenti caratteri è
l’operatore di concatenazione //. Per esempio, la concatenazione
tra le costanti di carattere string e beans si scrive come:

“string”//”beans”


Il risultato, stringbeans, può essere
assegnato ad una variabile di tipo carattere. Un esempio di concatenazione
utile è dato dal seguente programma.


program write_files
! Programma test per l’apertura di un file e la scrittura di dati
integer :: i,n
character(len = 15) :: file_name
n = 11
do i = 1,n
! assegna number.dat a file_name utilizzando lo statement write
write(unit=file_name,fmt=”(i2.2,a)”) i,”.dat”
! // è l’operatore di concatenazione
file_name = “config”//file_name
open (unit=1,file=file_name,action=”write”,status=”replace”)
write (unit=1, fmt=*) i*i,file_name
close(unit=1)
end do
end program write_files

In tale programma è da notare l’uso
dello statement write per costruire una stringa di caratteri
costituita da numeri e caratteri.


Variabili Complesse

Il programma che segue mostra come il Fortran 90 definisce ed utilizza le
variabili complesse.


program complex_example
integer, parameter :: double = 2
real (kind = double), parameter :: pi = 3.141592654
complex (kind = double) :: b,bstar,f,arg
real (kind = double) :: c
complex :: a
integer :: d
! Una costante complessa è scritta come due numeri reali separati
! da una virgola e racchiusi in parentesi.
a = (2,-3)
! Se una parte ha un kind, l’altra parte deve avere lo stesso kind
b = (0.5_double,0.8_double)
print *, “a =”, a ! notare che a ha una minore precisione rispetto b
print *, “a*a =”, a*a
print *, “b =”, b
print *, “a*b =”, a*b
c = real(b) ! real part of b
print *, “real part of b =”, c
c = aimag(b) ! parte immaginaria di b
print *, “imaginary part of b =”, c
d = int(a)
print *, “real part of a (converted to integer) =”, d
arg = cmplx(0.0,pi)
b = exp(arg) ! disposto su due linee per una più agevole lettura
bstar = conjg(b) ! complesso coniugato di b
f = abs(b) ! valore assoluto di b
print *, “properties of b =”, b,bstar,b*bstar,f
end program complex_example


Bibliografia

Walter S. Brainerd, Charles H.
Goldberg, and Jeanne C. Adams, Programmer’s Guide to F, Unicomp
(1996).
Michael Metcalf and John Reid, The
F Programming Language,
Oxford University Press (1996).

Le categorie della guida