Esplora E-book
Categorie
Esplora Audiolibri
Categorie
Esplora Riviste
Categorie
Esplora Documenti
Categorie
A. Crisanti
Versione 2, 2003
Revisione 1 (in corso), 2005
yaC-Primer
Questo Primer `e basato sulle lezioni tenute presso il Dipartimento di Fisica dellUniversit`a
di Roma La Sapienza nei corsi di Laboratorio di Calcolo e Laboratorio di Fisica Computazionale I per gli studenti del primo e secondo anno della Laurea triennale in Fisica.
Lo scopo principale di questo Primer `e quello di insegnare non solo il linguaggio C ma anche
la programmazione in linguaggio C. Non `e richiesta tuttavia nessuna esperienza o conoscenza
di programmazione precedente. Questo spiega la dovizia di particolari con cui sono discussi i
programmi negli esempi svolti presentando a volte il programma completo nelle sue fasi evolutive di sviluppo oppure programmi completi che risolvono lo stesso problema con soluzioni
diverse. Scopo di questo Primer `e infatti anche quello di cercare di insegnare un metodo di
programmazione e di risoluzione di problemi mediante luso di un computer. A scopo puramente didattico si suggerisce di provare a scrivere autonomamente il programma relativo ad
un dato esempio prima di guardare il programma proposto.
Gli esempi discussi sono le prove di laboratorio proposte agli studenti nella parte pratica
dei corsi e seguono lo sviluppo del Primer proponendo soluzioni adeguate alle conoscenze di
Linguaggio C acquisite fino a quel momento, `e quindi utile riconsiderare gli esercizi man mano
che le conoscenze aumentano per confrontare le diverse possibilit`a offerte dal linguaggio C.
Il linguaggio C `e un linguaggio general purpose, per cui pu`o essere utilizzato per molti scopi
differenti. In questo Primer lenfasi `e messa verso problemi e metodi utilizzati in fisica fornendo un primo approccio con la fisica computazionale.
` data facolt`
E
a a chiunque di utilizzare e/o distribuire gratuitamente il presente Primer ed i programmi
in esso contenuti per uso privato e/o didattico. Lutilizzo e/o la distribuzione per scopi diversi e/o a
fini di lucro `e vietato, salvo autorizzazione dellautore.
c
Copyright
2003
Andrea Crisanti
Indice
I.
Primer C
11
1. Computers
1.1. Computers Analogici e Digitali (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . .
1.2. Architettura di base di un computer digitale (Rev. 2.1) . . . . . . . . . . . . . .
1.3. Software di base di un computer digitale (Rev. 2.1) . . . . . . . . . . . . . . . .
13
13
15
18
2. Linguaggio C
2.1. Introduzione (Rev. 2.1.3) . . . . . . . . . . . . . . . . . . . .
2.2. Programmazione in Linguaggio C (Rev. 2.1.2) . . . . . . . .
2.2.1. Primo Programma in Linguaggio C . . . . . . . .
2.3. Elementi Lessicali (Rev. 2.1.2) . . . . . . . . . . . . . . . . .
2.3.1. Caratteri . . . . . . . . . . . . . . . . . . . . . .
2.3.2. Identificatori . . . . . . . . . . . . . . . . . . . .
2.3.3. Parole Chiave . . . . . . . . . . . . . . . . . . . .
2.3.4. Operatori e Separatori . . . . . . . . . . . . . . .
2.3.5. Commenti . . . . . . . . . . . . . . . . . . . . . .
2.4. Tipi base (Rev. 2.1.3) . . . . . . . . . . . . . . . . . . . . .
2.4.1. Tipo Intero . . . . . . . . . . . . . . . . . . . . .
2.4.2. Tipo Carattere . . . . . . . . . . . . . . . . . . .
2.4.3. Tipo Booleano . . . . . . . . . . . . . . . . . . .
2.4.4. Tipo Floating-Point . . . . . . . . . . . . . . . .
2.4.5. Tipo Complesso . . . . . . . . . . . . . . . . . .
2.5. Unit`a di Memoria (Rev. 2.1.5) . . . . . . . . . . . . . . . . .
2.6. Costanti (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . .
2.6.1. Costanti Intere . . . . . . . . . . . . . . . . . . .
2.6.2. Costanti Floating-Point . . . . . . . . . . . . . .
2.6.3. Costanti Carattere . . . . . . . . . . . . . . . . .
2.6.4. Costanti Stringa . . . . . . . . . . . . . . . . . .
2.7. Dichiarazione di variabili (Rev. 2.1.3) . . . . . . . . . . . . .
2.7.1. Inizializzazione . . . . . . . . . . . . . . . . . . .
2.7.2. Qualificatore const . . . . . . . . . . . . . . . .
2.7.3. Qualificatore volatile . . . . . . . . . . . . . .
2.8. Operatori (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . .
2.8.1. Operatori matematici . . . . . . . . . . . . . . .
2.8.2. Precedenza ed associativit`a . . . . . . . . . . . .
2.8.3. Operatori di incremento ++ e decremento --
23
23
26
27
32
32
34
35
36
37
37
38
42
44
45
48
48
50
50
52
53
55
55
57
57
58
58
58
60
61
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
62
64
65
66
67
67
68
70
71
73
74
77
78
79
81
83
84
87
95
103
103
104
105
106
108
109
114
120
128
130
133
139
140
141
142
145
146
146
152
159
160
162
169
170
yaC-Primer
298
299
300
301
305
311
317
317
319
319
320
322
324
324
327
329
330
332
338
338
340
342
344
348
350
351
352
354
356
357
360
369
A. Appendici
373
A.1. Compilazione ed esecuzione di un programma in C in ambiente UNIX (Rev. 2.1) 373
A.2. Breve introduzione a GNU Emacs (Rev. 2.0) . . . . . . . . . . . . . . . . . . . . 375
A.3. Miniguida ai comandi essenziali di UNIX (Rev. 2.0) . . . . . . . . . . . . . . . . 378
A.4. Sistemi di numerazione digitale (Rev. 2.0) . . . . . . . . . . . . . . . . . . . . . . 379
A.4.1. Sistema decimale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
A.4.2. Sistema binario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
A.4.3. Sistema ottale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
A.4.4. Sistema esadecimale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.5. Codici ASCII (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
A.6. Precedenza ed Associativit`a degli operatori (Rev. 2.1) . . . . . . . . . . . . . . . 384
A.7. Modulo yac utils.c (Rev. 2.0.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
yaC-Primer
II. Applicazioni
391
2. Radici di equazioni
393
2.1. Metodo della bisezione (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
2.2. Bracketing delle radici (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . 401
3. Integrazione Numerica
409
3.1. Calcolo numerico di integrali (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . 409
3.1.1. Metodo dei Rettangoli . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
3.1.2. Metodo dei Trapezi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
3.1.3. Metodo di Simpson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
3.1.4. Metodo di Adams-Bashford . . . . . . . . . . . . . . . . . . . . . . . . 413
3.1.5. Metodo Monte Carlo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
3.2. Errore dellintegrazione con il metodo Monte Carlo (Rev. 2.1) . . . . . . . . . . . 414
3.3. Equazioni Differenziali Ordinarie: Algoritmo di Eulero (Rev. 2.1) . . . . . . . . . 418
3.4. Esempio: Integrazione dellequazione del moto del pendolo con lalgoritmo di
Eulero (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
3.5. Equazioni Differenziali Ordinarie del secondo ordine: Algoritmo di Verlet (Rev. 2.1) 429
3.6. Esempio: Integrazione dellequazione del moto del pendolo con lalgoritmo di
Verlet (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
3.7. Esempio: Moto di due pianeti con algoritmo di Verlet (Rev. 2.0.4) . . . . . . . . 436
3.8. Integrazione di Equazioni Differenziali Ordinarie: Algoritmo Runge-Kutta (Rev.
2.0.1)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
3.8.1. Schema del secondo ordine . . . . . . . . . . . . . . . . . . . . . . . . 450
3.8.2. Schema del quarto ordine . . . . . . . . . . . . . . . . . . . . . . . . . 451
3.9. Esempio: Moto di due pianeti in presenza di attrito (Rev. 2.0.2) . . . . . . . . . 452
3.10. Esempio: Oscillatore Armonico Forzato (Rev. 2.0.4) . . . . . . . . . . . . . . . . 465
3.11. Esempio: Studio dellOscillatore Armonico Forzato da una Sinusoide (Rev. 2.0.1) 474
3.12. Esempio: Moto di un punto soggetto ad una forza centrale generata da due
molle (Rev. 2.0) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
4. Strutture Dati
4.1. Single Linked Lists (Rev. 2.0.2) .
4.2. Stack (Rev. 2.0.4) . . . . . . . . .
4.3. Double Linked Lists (Rev. 2.0.2)
4.4. Alberi Binari (Rev. 2.0.3) . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5. Numeri Random
5.1. Generatori di Numeri Random (Rev. 2.0.1) . . . . .
5.2. Metodo delle Congruenze Lineari (Rev. 2.0.1) . . . .
5.2.1. rand(), srand(), RAND MAX . . . . . . . .
5.3. Semplice generatore di numeri Gaussiani (Rev. 2.0.1)
5.4. Metodo dellInversione (Rev. 2.0.1) . . . . . . . . . .
5.4.1. Distribuzione uniforme in [a, b] . . . . . .
5.4.2. Distribuzione Esponenziale . . . . . . . .
5.4.3. Distribuzione Gaussiana . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
495
495
505
512
519
.
.
.
.
.
.
.
.
531
531
532
536
537
539
540
541
541
(Rev. 2.0.2)
. . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
542
551
551
557
563
567
7. Automi Cellulari
581
7.1. Automi Cellulari (Rev. 2.0.3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581
7.2. Automi Cellulari Booleani Bidimensionali: Game of Life (Rev. 2.0.3) . . . . . . . 592
7.2.1. Animazione con gnuplot . . . . . . . . . . . . . . . . . . . . . . . . . 605
8. Percolazione
607
8.1. Percolazione (Rev. 2.0.4) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
8.1.1. Semplice studio numerico della soglia di percolazione . . . . . . . . . . 608
8.2. Soglia di Percolazione di Sito (Rev. 2.0) . . . . . . . . . . . . . . . . . . . . . . . 617
9. Compressione delle Imagini
9.1. Compressione delle immagini: Iterated Function System
(Rev. 2.0.1)
A. Appendici
A.1. Esempio: Moto di due pianeti con algoritmo di Runge-Kutta
10
. . . . . . .
(Rev. 2.0)
621
621
627
. . . . . 627
Part I.
Primer C
11
390
Part II.
Applicazioni
391
2. Radici di equazioni
2.1. Metodo della bisezione
(Rev. 2.1)
Supponiamo di dover determinare le radici reali di una funzione a valori reali f (x), ossia le
soluzioni reali dellequazione
f (x) = 0,
(2.1)
in un intervallo [a, b] non necessariamente noto a priori.
Se la funzione f (x) `e sufficientemente semplice, ad esempio un polinomio di primo o secondo
grado in x, allora `e possibile determinare le soluzioni mediante formule esplicite. Nel caso
generale, tuttavia, non esistono formule esplicite per cui per determinare le soluzioni si deve
ricorrere a metodi di soluzione approssimati sia analitici che numerici.
Vi sono vari metodi o algoritmi numerici per determinare numericamente le radici reali
dellequazione (2.1), qui considereremo quello detto della bisezione. Il punto di partenza
del metodo `e losservazione che se la funzione continua f (x) assume valori di segno opposto
agli estremi di un dato intervallo [a, b] allora nellintervallo vi devono essere un numero dispari di soluzioni dellequazione (2.1). Il metodo della bisezione assume che nellintervallo
[a, b] vi sia una ed una sola soluzione dellequazione. Se nellintervallo vi sono pi`
u soluzioni
`e necessario isolarle suddividendo lintervallo in sottointervalli ciascuno contenente una sola
soluzione prima di poter utilizzare il metodo della bisezione.
Assumendo che nellintervallo [a, b] vi sia una sola radice dellequazione (2.1) il metodo della
bisezione ne stima il valore dividendo successivamente lintervallo che contiene la radice in
due sottointervalli di uguale lunghezza, da qui il nome del metodo.
intervallo iniziale
f(x)
prima divisione
seconda divisione
...
x
Radice f(x) = 0
Ad ogni iterazione la lunghezza dellintervallo che contiene la soluzione viene dimezzata per
cui la lunghezza dellintervallo diminuisce in modo esponenziale con il numero di iterazioni. In
393
(Rev. 2.1)
generale non ci si pu`o aspettare di trovare la radice esatta dellequazione (2.1), di conseguenza
il procedimento si arresta quando la lunghezza dellintervallo contenente la soluzione `e minore
od uguale di un valore prefissato che rappresenta la precisione numerica con cui viene stimata
la soluzione.
Lintero algoritmo viene facilmente scritto in forma iterativa. Per prima cosa si valuta la
funzione nel punto di centrale m = (a + b)/2 dellintervallo che contiene la soluzione. Nel caso
molto improbabile che f (m) = 0 la soluzione `e stata trovata. Nel caso contrario se f (m) `e di
segno opposto a f (b) la radice si trova nellintervallo [m, b] altrimenti si trova nellintervallo
[a, m]. Se la lunghezza dei semi-intervalli `e maggiore della precisione fissata il procedimento di
divisione viene ripetuto prendendo al posto dellintervallo iniziale [a, b] il semi-intervallo [a, m]
o [m, b] che contiene la radice. Se invece si `e raggiunta la precisione fissata il procedimento
si arresta ed il valore di m, o di qualsiasi altro punto nellintervallo che contiene la soluzione,
fornisce una stima della radice.
La seguente funzione
double bisect ( double ( fun ) ( double ) , double a , double b ,
double xacc )
implementa il metodo della bisezione. La funzione bisect() prende come parametri il puntatore fun alla funzione di cui si vuole trovare la radice, gli estremi a e b dellintervallo iniziale
[a, b] in cui si trova la radice e la precisione xacc richiesta. Per evitare di entrare in un ciclo
infinito la funzione effettua al massimo un numero BIS MAX di bisezioni. La macro BIS MAX `e
locale alla funzione. La funzione ritorna il valore stimato della soluzione o NaN (not-a-number)
nel caso la soluzione non possa essere trovata.
Funzione: bisect()
/*
* Descrizione : trova la radice di una funzione con il metodo
*
della bisezione
*
* Input
:
fun
: funzione
*
x1 , x2 : estremi intervallo
*
xacc
: precisione voluta
*
* Output
: radice della funzione o NaN
*
* $yaC - Primer : bisect1 .c v 3.1 20.02.05 AC $
*/
# include < s t d l i b . h>
# include <math . h>
# include <s t d i o . h>
/* Macros */
# define BIS MAX 40
394
double x_mid ;
double y1 , y2 ;
double y_mid ;
double
dx ;
/*
/*
/*
/*
(Rev. 2.1)
*/
*/
*/
*/
Calcola il valore della funzione agli estremi dellintervallo. Nello Standard C si pu`
o
omettere loperatore di dereferenza * per cui si pu`o anche scrivere
y1 = fun ( x1 ) ;
y2 = fun ( x2 ) ;
I valori che la funzione assume agli estremi dellintervallo sono salvati per evitare di
doverli ricalcolare pi`
u volte.
if ( ( y1 y2 ) > 0 . 0 )
return strtod ( "NAN" , NULL ) ;
Se agli estremi dellintervallo iniziale la funzione ha lo stesso segno il metodo non pu`o
essere utilizzato. Lesecuzione della funzione si interrompe e la funzione bisect() restituisce il valore NaN (not-a-number) ottenuto con la chiamata
395
(Rev. 2.1)
che restituisce il valore della stringa NAN, il valore NaN per lappunto. Strettamente
parlando la conversione della stringa NAN nel valore not-a-number `e stata introdotta nello
Standard C99, per cui questa chiamata `e illegale nello Standard C89. In questo esempio
`e stata utilizza a scopo illustrativo, facendo uneccezione alla regola di presentare solo
programmi in C89, perche a volte pu`o essere utile che una funzione possa restituire il
valore NaN, o pi`
u in generale poter generare il valore NaN senza che il programma termini
con un errore. A riprova dellutilit`a alcuni compilatori accettano la chiamata anche se
viene specificato esplicitamente di utilizzare lo Standard C89, il compilatore GNU C `e
tra questi.
Per maggiori informazioni sulla funzione strtod() e NaN si rimanda alla fine di questa
sezione.
dx
= 0 . 5 ;
x_mid = x1 + dx ;
y_mid = ( fun ) ( x_mid ) ;
Lintervallo viene diviso in due e viene calcolato il punto medio dellintervallo x mid ed il
valore della funzione nel punto medio y mid. Il punto medio `e determinato aggiungendo
allestremo inferiore dellintervallo x1 met`a della lunghezza dellintervallo.
if ( ( y1 y_mid ) > 0 . 0 ) {
x1 = x_mid ;
y1 = y_mid ;
}
Se il segno della funzione nel punto medio x mid e allestremo inferiore x1 sono concordi la radice si trova nel semi-intervallo con estremo inferiore x mid, per cui si sposta
lestremo inferiore, salvando contemporaneamente il valore della funzione per evitare di
doverlo calcolare nuovamente.
if ( dx < xacc | | y_mid == 0 . 0 ) return x_mid ;
la lunghezza dellintervallo `e minore della precisione richiesta, o se la funzione nel punto
medio `e nulla, lesecuzione della funzione viene interrotta e la funzione ritorna come
stima della radice il valore di x mid.
for ( bis = 0 ; bis < BIS_MAX ; ++bis ) {
...
}
return strtod ( "NAN" , NULL ) ;
Se la funzione non `e riuscita a trovare la soluzione con la precisione richiesta nel numero
massimo di bisezioni BIS MAX la funzione restituisce NaN. Altre strategie sono possibili
ad esempio la funzione pu`o restituire uno dei due estremi dellintervallo iniziale. Qui si
`e scelto di utilizzare il valore NaN per illustrare il suo uso.
396
(Rev. 2.1)
Per determinare se il segno della funzione nel punto medio dellintervallo `e concorde con
quello della funzione allestremo inferiore dellintervallo la funzione bisect1.c effettua una
moltiplicazione ad ogni iterazione. Questa moltiplicazione pu`o essere eliminata osservando
che il punto medio dellintervallo pu`o essere calcolato sia aggiungendo allestremo inferiore
x1 met`a della lunghezza dellintervallo che sottraendo met`a della lunghezza dellintervallo
dallestremo superiore x2. Di conseguenza se si fissa il segno della funzione allestremo di
riferimento, sia questo lestremo superiore o quello inferiore, basta controllare il segno della
funzione nel punto medio eliminando cos` la moltiplicazione.
Funzione: bisect()
/*
* Descrizione : trova la radice di una funzione con il metodo
*
della bisezione
*
* Input
:
fun
: funzione
*
x1 , x2 : estremi intervallo
*
xacc
: precisione voluta
*
* Output
: radice della funzione o NaN
*
* $yaC - Primer : bisect2 .c v 3.1 20.02.05 AC $
*/
# include < s t d l i b . h>
# include <math . h>
# include <s t d i o . h>
/* Macros */
# define BIS MAX 40
397
}
else {
x_ref = x2 ;
dx
= x1 x2 ;
}
(Rev. 2.1)
/* dx < 0 */
/* dx > 0 */
/* dx < 0 */
La funzione utilizza come estremo di riferimento quello dove la funzione fun() `e negativa. Nel caso questo coincida con lestremo superiore il valore di dx `e negativo poiche
il valore del punto medio `e minore del valore di riferimento x ref.
if ( y_mid < 0 . 0 ) x_ref = x_mid ;
Se la funzione nel punto medio `e negativa la radice si trova alla sinistra del punto medio
e quindi si prende questultimo come nuovo punto di riferimento.
if ( fabs ( dx ) < xacc | | y_mid == 0 . 0 ) return x_mid ;
Siccome dx pu`o essere sia positivo che negativo nel controllo si usa la funzione
# include <math . h>
double fabs ( double x ) ;
398
(Rev. 2.1)
Test della funzione: Il seguente programma mostra luso della funzione bisect() utilizzando la funzione di prova f (x) = x + 0.1.
Programma: test-bisect.c
/*
* Descrizione : test funzione bisect ()
*
* $yaC - Primer : test - bisect .c, v 2.1 24.02.2004 AC $
*/
# include <math . h>
# include <s t d i o . h>
static double f ( double ) ;
extern double bisect ( double ( func ) ( double ) , double a ,
double acc ) ;
double b ,
La funzione di prova f() `e dichiarata in classe static poiche il suo scopo `e limitato al solo
file test-bisect.c. La funzione bisect() `e dichiarata extern poiche la sua definizione si
trova in un file diverso da test-bisect.c. Tutti i files necessari devono essere compilati e
poi uniti dal linker per produrre un eseguibile. In questo caso ci`o si ottiene con il comando
$ cc testbisect . c bisect2 . c
Se per qualche motivo la funzione non pu`o determinare una stima numerica della soluzione
si ha come output
radice : nan
funzione : nan
399
(Rev. 2.1)
Lo Standard C89 non fornisce una funzione per determinare se il valore di una espressione `e
NaN, questa `e stata introdotta nel C99.
Funzione strtod(), NaN e macro isnan
La funzione string-to-double
# include < s t d l i b . h>
double strtod ( const char str , char ptr ) ;
converte la parte iniziale della stringa puntata dal puntatore str fino al primo carattere bianco
in un numero di tipo double. Eventuali caratteri bianchi iniziali sono ignorati. La funzione
ritorna il valore convertito in tipo double.
Il formato della stringa deve essere un segno opzionale + o - seguito dalla rappresentazione di un numero floating-point in digits decimali o esadecimali. Nel secondo caso la
rappresentazione deve essere preceduta dai caratteri 0X o 0x. In entrambi i casi `e possibile
specificare un esponente precedendo il valore dellesponente positivo o negativo dal carattere
e o E per la notazione decimale e p o P per la notazione esadecimale. Nella notazione
decimale lesponente indica la potenza di 10 per cui deve essere moltiplicato il valore della
parte non-esponenziale. Nella notazione esadecimale si usano potenze di 2.
Se la stringa inizia con INFINITY o NAN, indipentemente dai caratteri maiscoli o minuscoli,
il valore viene interpretato rispettivamente come un infinito o un NaN.
La conversione di un numero in formato esadecimale, infinito e NaN con la funzione strtod()
`e stata introdotta nel C99.
Se il puntatore ptr `e diverso dal puntatore nullo NULL il puntatore al primo carattere della
stringa dopo lultimo carattere utilizzato nella conversione viene salvato nella locazione di
memoria indicata da ptr. In questo modo `e possibile convertire tutta una stringa composta
da una sequenza di digits separati da spazi bianchi con chiamate successive della funzione
strtod(). Se il puntatore ptr `e il puntatore nullo NULL ptr viene ignorato.
Se non `e possibile effettuare la conversione la funzione ritorna il valore 0 e viene assegnato il
valore ERANGE alla variabile errno. Se ptr `e diverso da NULL *ptr contiene il valore di str,
ossia lindirizzo di memoria della stringa.
Lo Standard C89 non fornisce una utility per controllare se il valore di una espressione `e
uguale NaN. Nel C99 `e possibile controllare se un valore `e NaN utilizzando la macro
# include <math . h>
int isnan ( double x ) ;
400
yaC-Primer: Bracketing
(Rev. 2.1)
/*
* Descrizione : test funzione bisect ()
*
* $yaC - Primer : test - bisect_nan .c, v 2.1 24.02.2004 AC $
*/
# include <math . h>
# include <s t d i o . h>
static double f ( double ) ;
extern double bisect ( double ( func ) ( double ) , double a ,
double acc ) ;
double b ,
Alcuni compilatori, tra cui il GNU C, accettano sia la chiamata strtod("NAN", NULL) che
la macro isnan anche se viene specificato esplicitamente di utilizzare lo Standard C89. Nonstante ci`o `e bene ricordare che strettamente parlando queste sono illegali nel C89.
(Rev. 2.1)
401
yaC-Primer: Bracketing
(Rev. 2.1)
lintervallo, o gli intervalli, che contengono una sola soluzione. Questo procedimento di isolare
le soluzioni `e chiamato bracketing.
Le due funzioni seguenti effettuano il bracketing sfruttando la propriet`a che se nellintervallo
[a, b] vi `e una radice dellequazione allora la funzione continua f (x) assume valori di segno
opposto agli estremi:
f (a) f (b) < 0
In realt`a questa `e una condizione necessaria ma non sufficiente per lesistenza di una sola
soluzione, infatti questa assicura solo che nellintervallo vi `e un numero dispari di soluzioni.
Di conseguenza queste funzioni vanno utilizzate con un p`o di cautela.
La prima funzione
int root_brack1 ( double ( func ) ( double ) , double a , double b ) ;
determina un intervallo tale che la funzione f (x) assuma valori di segno opposto agli estremi
allargando lintervallo [a, b] iniziale fornito fino a quando la condizione non `e verificata. La
funzione prende come parametri il puntatore alla funzione f (x) ed i puntatori alle variabili
con i valori iniziali degli estremi a e b dellintervallo. Se la funzione non riesce a determinare
lintervallo voluto ritorna il valore 1 altrimenti ritorna il valore 0 ed il valore degli estremi
dellintervallo trovato nelle variabili a e b.
Funzione: root brack1()
/*
* Descrizione : dato un intervallo iniziale lo allarga
*
fino a quando la funzione fun () non assume
*
valori di segno opposto agli estremi
*
* Input
: fun
: funzione
*
*a, *b
: valori iniziali estremi intervallo
*
* Output
: *a, *b
: valori finali estremi intervallo
*
* $yaC - Primer : root_brack1 .c v 1.1 20.02.05 AC $
*/
# include <math . h>
/* Macros locali */
# define SCALE
1.414
# define N TRY
50
402
yaC-Primer: Bracketing
(Rev. 2.1)
f_b = func ( b ) ;
for ( i = 0 ; i < N_TRY ; ++i ) {
if ( f_a f_b < 0 . 0 ) return 0 ;
/* trovato */
/* Non trovato */
La funzione usa due macros locali: SCALE per il fattore di scala con cui viene allargato
lintervallo, e N TRY per il numero massimo di espansioni. Per ottimizzare la ricerca lespansione viene effettuata spostando sempre lestremo dellintervallo in cui la funzione assume il valore
minore in modulo:
if ( fabs ( f_a ) < fabs ( f_b ) ) {
a = b + SCALE ( a b ) ;
f_a = func ( a ) ;
}
else {
b = a + SCALE ( b a ) ;
f_b = func ( b ) ;
}
Per evitare di entrare in un ciclo infinito lespansione viene effettuata al massimo N TRY volte.
La seconda funzione root brack2() divide lintervallo iniziale [a, b] dato in n sottointervalli di
lunghezza dx = (b a)/n e determina in quale di questi la funzione f (x) assume agli estremi
valori di segno opposto. Il prototipo della funzione `e
void root_brack2 ( double ( func ) ( double ) , double a , double b , int n ,
int nb , double x1 [ ] , double x2 [ ] ) ;
La funzione root brack2() prende come parametri il puntatore alla funzione f (x), gli estremi
dellintervallo iniziale a e b ed il numero n di sotto intervalli. Il puntatore nb punta ad una
variabile di tipo int il cui valore `e il numero richiesto di sottointervalli in cui la funzione
assume valori differenti agli estremi. Il numero di sottointervalli trovati viene assegnato alla
variabile puntata dal puntatore nb e gli estremi degli intervalli salvati nei due arrays x1 e x2:
x1[0], x2[0]
primo intervallo
x1[1], x2[1]
secondo intervallo
403
yaC-Primer: Bracketing
(Rev. 2.1)
x1[nb 1], x2[nb 1]
ultimo intervallo
La dimensione degli arrays x1 e x2 deve essere non inferiore al numero di intervalli richiesto.
La funzione root brack2() non controlla che gli arrays x1 e x2 siano di dimensione sufficiente.
Se non viene trovato nessun intervallo alla variabile *nb viene assegnato il valore 0.
Funzione: root brack2()
/*
* Descrizione : Divide lintervallo dato in n sottointervalli
*
e determina per quali intervalli la funzione fun ()
*
assume valori di segno opposto agli estremi
*
* Input
: fun
: funzione
*
a, b
: estremi intervallo
*
n
: # suddivisioni
*
*nb
: # intervalli richiesti
*
* Output
: *nb
: # intervalli trovati , 0 se non trovati
*
x1[], x2 [] : estremi intervalli trovati
*
* $yaC - Primer : root_brack2 .c v 1.1 20.02.05 AC $
*/
void root_brack2 ( double ( func ) ( double ) , double a , double b , int n ,
int nb , double x1 [ ] , double x2 [ ] )
{
int
nbb ;
/* numero di intervalli richiesti */
int
i;
double
x , dx ;
double f_1 , f_2 ;
/* numero di intervalli richiesti */
nbb = nb ;
/* lunghezza sottointervalli */
dx = ( b a ) / ( double ) n ;
nb = 0 ;
/* contatore intervalli trovati a zero */
x
= a;
/* ricerca inizia estremo a
*/
f_1 = ( func ) ( a ) ;
for ( i = 0 ; i< n ; ++i ) {
x += dx ;
f_2 = ( func ) ( x ) ;
if ( f_1 f_2 < 0 . 0 ) {
/* trovato un intervallo
*/
x1 [ nb ] = x dx ;
x2 [ nb ] = x ;
++(nb ) ;
/* contatore intervalli +1 */
/* se trovati intervalli richiesti esci */
if ( nb == nbb ) return ;
}
404
yaC-Primer: Bracketing
(Rev. 2.1)
f_1 = f_2 ;
}
return ;
}
La funzione utilizza la variabile interna nbb per salvare il il numero di intervalli richiesti e
poter cos` utilizzare *nb per contare il numero di intervalli effettivamente trovati. Le variabili
f 1 e f 2 sono introdotte per effettuare una sola chiamata a funzione per estremo di intervallo:
f_1 = ( func ) ( a ) ;
for ( i = 0 ; i< n ; ++i ) {
x += dx ;
f_2 = ( func ) ( x ) ;
if ( f_1 f_2 < 0 . 0 ) {
...
}
f_1 = f_2 ;
}
Se la valutazione della funzione f (x) richiede parecchio tempo di calcolo questo semplice
accorgimento pu`o ridurre drasticamente i tempi esecuzione della funzione root brack2().
La ricerca degli intervalli inizia dal sottointervallo con estremo a
x = a;
oppure quando sono stati controllati tutti gli n sotto intervalli. In ogni caso il valore di *nb
`e uguale al numero di intervalli trovati, o 0 se non sono stati trovati intervalli.
Test delle funzioni: Il seguente programma mostra luso delle funzioni root brack1() e
root brack2() per determinare le soluzioni dellequazione
x2 4 = 0
Programma: test-brack.c
/*
* Descrizione : test funzioni bracketing
*
* $yaC - Primer : test - brack.c, v 2.1 24.02.2004 AC $
*/
# include <s t d i o . h>
# include <math . h>
405
yaC-Primer: Bracketing
extern int
extern void
(Rev. 2.1)
double acc ) ;
root_brack1 ( double ( func ) ( double ) , double a ,
double b ) ;
root_brack2 ( double ( func ) ( double ) , double a , double b ,
int n , int nb_p , double x1 [ ] , double x2 [ ] ) ;
[%f, %f]\n" , a , b ) ;
{
: [%f, %f]\n" , a , b ) ;
: %f +/- %f\n" , root , acc ) ;
: %f\n\n" , f ( root ) ) ;
a = 5.5;
b = 5.5;
nb = 2 ;
printf ( "\n" ) ;
root_brack2 ( f , a , b , 5 , &nb , x_1 , x_2 ) ;
if ( nb ) {
printf ( "2) trovati %d intervalli \n\n" , nb ) ;
for ( i = 0 ; i < nb ; ++i ) {
printf ( "2) intervallo trovato : [%f, %f]\n" , x_1 [ i ] , x_2 [ i ] ) ;
root = bisect ( f , x_1 [ i ] , x_2 [ i ] , acc ) ;
printf ( "2) radice
: %f +/- %f\n" , root , acc ) ;
printf ( "2) funzione
: %f\n\n" , f ( root ) ) ;
}
}
return 0 ;
}
double f ( double x )
{
return ( xx 4 . 0 ) ;
}
406
yaC-Primer: Bracketing
(Rev. 2.1)
intervallo iniziale : [ 1 . 0 0 0 0 0 0 , 0 . 0 0 0 0 0 0 ]
intervallo trovato : [ 2 . 4 1 4 0 0 0 , 0 . 0 0 0 0 0 0 ]
radice
: 2.000051 +/ 0 . 0 0 0 1 0 0
funzione
: 0.000206
2 ) trovati 2 intervalli
2 ) intervallo trovato
2 ) radice
2 ) funzione
: [ 3 . 3 0 0 0 0 0 , 1.100000]
: 1.999994 +/ 0 . 0 0 0 1 0 0
: 0.000024
2 ) intervallo trovato
2 ) radice
2 ) funzione
: [1.100000 , 3.300000]
: 1 . 9 9 9 9 9 4 +/ 0 . 0 0 0 1 0 0
: 0.000024
Loutput mostra che in tutti i casi le soluzioni sono state dererminate dalla funzione bisect()
con la precisione richiesta.
407
yaC-Primer: Bracketing
408
(Rev. 2.1)
3. Integrazione Numerica
3.1. Calcolo numerico di integrali
(Rev. 2.1.1)
Un problema che spesso ricorre `e quello di dover calcolare lintegrale di una funzione f (x) a
valori reali in un intervallo [a, b]
Z b
f (x) dx
I=
a
I metodi di integrazione numerica, chiamati anche quadrature, sono stati sviluppati molto
prima dellinvenzione dei calcolatori elettronici. Infatti se le derivata di una funzione pu`o
sempre essere calcolata, in generale gli integrali anche di funzioni elementari non possono
essere calcolati analiticamente. Per questo motivo gi`a nel 18-esimo e 19-esimo secolo sono
stati sviluppati metodi numerici per il calcolo degli integrali.
Quasi tutti i metodi di quadratura numerici si basano, in un modo o nellaltro, sullidea
di dividere lintervallo di integrazione in intervalli pi`
u piccoli, stimare lintegrale su ciascun
intervallo sfruttando il fatto che sono piccoli e risommare il contributo di tutti gli intervalli.
Chiaramente lidea `e quella di riuscire a stimare il pi`
u accuratamente possibile lintegrale con
il numero minimo di intervallini.
Nel seguito considereremo alcuni degli algoritmi pi`
u noti. Abbiamo incluso il metodo Monte
Carlo, sebbene sia un algoritmo stocastico, perche `e facilmente estendibile ad integrali in pi`
u
di una dimensione.
f(x)
1111
0000
0000
1111
0000
1111
0000
1111
0000
1111
0000
1111
0000
1111
0000
1111
0000
1111
0000
1111
x0
x1
x1
f (x) dx ' f
x0
x0 + x1
2
(x1 x0 )
409
(Rev. 2.1.1)
f (x) dx '
a
N
1
X
f
i=0
xi+1 + xi
2
h
con x0 = a, xN = b.
Il metodo dei rettangoli `e chiaramente esatto se la funzione `e costante, inoltre la scelta del
punto medio fa si che il metodo sia esatto anche per funzioni lineari di conseguenza lerrore
`e O(h3 ), come `e facile verificare usando successivamente le funzioni di prova: f (x) = 1,
f (x) = x e f (x) = x2 . Per le prime due infatti la formula dei rettangoli fornisce la soluzione
esatta mentre per la terza lintegrale esatto e quello ottenuto con la formula differiscono per
il termine di ordine O(h3 ).
f(x)
11111
00000
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
00000
11111
x
x
0
x1
i (x x )
1
0
f (x1 ) f (x0 )
2
h
i (x x )
1
0
= f (x0 ) + f (x1 )
2
+
N
1
X
h
h
f (x) dx ' f (x0 ) +
h f (xi ) + f (xN )
2
2
i=1
410
(Rev. 2.1.1)
della funzione agli estremi dellintervallo. Siccome lintegrale `e lineare sia in f (x) che in h, la
formula pi`
u generale `e:
Z x+h
h
i
f (x) dx = h a f (x) + b f (x + h)
(3.1)
x
Il valore dei due parametri a e b viene fissato richiedendo che la formula sia esatta almeno per
alcune funzioni. Le uniche funzioni che sono univocamente determinate conoscendo il loro
valore in due punti distinti sono le funzioni costanti e le funzioni lineari, per cui la formula
(3.1) pu`o essere esatta solo per queste funzioni.
Senza perdere di generalit`a come funzioni di prova possiamo usare f (x) = 1 ed f (x) = x
perche per lomogeneit`a dellespressione (3.1) e ladditivit`a dellintegrale se la formula `e esatta
per queste funzioni `e esatta per ogni funzione costante o lineare. Inserendo queste funzioni
nellespressione (3.1) e valutando entrambi i termini si ottiene
f (x) = 1
x+h
h
i
dx = h = h a + b
a+b=1
f (x) = x
Z
x+h
x dx =
x
h
i
1
[(x + h)2 x2 ] = h a x + b (x + h)
2
h2
hx +
= h (a + b) x + b h2
2
a+b =
b
=
1
1
2
x+h
f (x) dx =
x
i
hh
f (x) + f (x + h)
2
in cui si riconosce la formula dei Trapezi. Utilizzando f (x) = x2 si vede facilmente che
lerrore della formula dei trapezi `e O(h3 ) in quando i termini proporzionali a h3 ai due lati
delluguaglianza differiscono tra loro.
Questa derivazione della formula dei trapezi mostra chiaramente che per ottenere formule pi`
u
precise, ossia con un errore O(hn ) con n > 3, non basta conoscere il valore della funzione agli
u informazioni sulla funzione.
estremi dellintervallo ma `e necessario utilizzare pi`
La formula di Simpson pu`o essere derivata dalla seguente espressione che utilizza i valori della
funzione su due intervalli consecutivi:
Z x+2h
h
i
f (x) dx = h a f (x) + b f (x + h) + c f (x + 2h)
x
411
(Rev. 2.1.1)
imponendo che questa sia esatta per le funzioni fino al secondo grado incluso.
Come fatto per la derivazione della formula dei trapezi il valore delle costanti a, b e c pu`
o
essere ottenuto facilmente utilizzando le funzioni di prova f (x) = 1, f (x) = x e f (x) =
x2 . Sostituendo queste funzioni nella precedente espressione e valutando entrambi i termini
dellespressione si ottiene:
f (x) = 1
Z
x+2h
h
i
dx = 2h = h a + b + c
a+b+c=2
f (x) = x
Z
x+2h
h
i
1
2
2
x dx = [(x + 2h) x ] = h a x + b (x + h) + c (x + 2h)
2
2
2hx + 2h = h (a + b + c) x + (b + 2c) h
a+b+c = 2
b + 2c
= 2
poiche luguaglianza deve essere vera per ogni valore di h. Di nuovo la prima condizione
`e uguale a quella ottenuta con la funzione f (x) = 1.
f (x) = x2
Z x+2h
x
x2 dx =
i
h
1
[(x + 2h)3 x3 ] = h a x2 + b (x + h)2 + c (x + 2h)2
3
Analizzando le uguaglianze ottenute per f (x) = 1 e f (x) = x notiamo che i termini proporzionali a x non forniscono nessuna nuova condizione. Possiamo quindi semplificare i
calcoli prendendo x = 0. La precedente uguaglianza si riduce quindi a
8 3
h = (b + 4c) h3
3
a+b+c =
b + 2c
=
b + 4c
=
b + 4c =
8
3
sistema di equazioni
2
2
8
3
che ha come soluzione a = 1/3, b = 4/3 e c = 1/3. Sostituendo questi valori nellespressione
di partenza si ottiene la formula di Simpson
f(x)
Z
f (x) dx '
x
412
x+h x+2h
x+2h
i
hh
f (x)+4 f (x+h)+f (x+2h)
3
(Rev. 2.1.1)
Dalla derivazione segue che lerrore commesso `e O(h4 ). In realt`a a causa di cancellazioni la
formula di Simpson `e esatta anche per funzioni di terzo ordine, come pu`o essere facilmente
verificato con la funzione di prova f (x) = x3 , per cui lerrore `e O(h5 ).
Se lintervallo [a, b] viene diviso in 2N intervalli di ampiezza h = (a b)/2N si ottiene la
formula di Simpson estesa
Z b
i
hh
f (x) dx '
f (x0 )+4 f (x1 )+2 f (x2 )+4 f (x3 )+ +2 f (x2N 2 )+4 f (x2N 1 )+ f (x2N )
3
a
con x0 = a, x2N = b poiche il contributo degli intervalli adiacenti si somma.
commesso nella stima dellintegrale diminuisce come 1/ n con il numero di punti n utilizzati.
Per spiegare il metodo Monte Carlo supponiamo di dover calcolare lintegrale
Z b
I=
f (x) dx
a
413
(Rev. 2.1)
A tal fine si prendono n punti xi (i = 1, . . . , n) a caso scelti con una distribuzione di probabilit`
a
P (x) tale che:
P (x) 6= 0 per x [a, b]
e normalizzata sullintervallo [a, b]
Z
P (x) dx = 1
a
Siccome P (x) non si annulla mai nellintervallo di integrazione possiamo definire la variabile
casuale ausiliaria
f (x)
s(x) =
axb
P (x)
il cui valore aspettato `e
Z
hsi =
s(x) P (x) dx
a
Z
=
a
f (x)
P (x) dx =
P (x)
f (x) dx
a
n
ba X
f (x) dx '
f (xi )
n
i=1
Chiaramente a seconda della funzione da integrare questa scelta della P (x) potrebbe non
essere la migliore. Il problema di come effettuare una buona scelta della distribuzione di
probabilit`a P (x) con cui scegliere i punti xi esula tuttavia dai nostri scopi.
(Rev. 2.1)
Il metodo Monte Carlo per valutare lintegrale di una funzione f (x) su un intervallo [a, b]
utilizza i valori della funzione integranda per n punti xi (i = 1, . . . , n) scelti nellintervallo di
integrazione [a, b] con una distribuzione di probabilit`a P (x) fissata. Il metodo Monte Carlo
414
(Rev. 2.1)
`e quindi un metodo statistico perche i punti su cui valutare la funzione non sono fissati a
priori ma sono scelti a caso. In generale scelte diverse dei punti xi per uno stesso valore di n
forniscono stime diverse dellintegrale. Lampiezza (n) delle fluttuazioni del valore stimato
dellintegrale al variare delle sequenze di n punti utilizzati rappresenta lerrore della stima
` ragionevole aspettarsi che allaumentare del
numerica dellintegrale valutato con n punti. E
numero n di punti utilizzati (n) diminuisca, e si annulli nel limite teorico n .
Un modo molto semplice di studiare landamento di (n) `e quello di generare K sequenze
indipendenti di n punti xi con cui ottenere K stime dellintegrale Ik (n) (k = 1, . . . , K).
Lampiezza delle fluttuazioni (n) `e data dalla deviazione standard dei K valori ottenuti:
(n)2 = hI(n)2 i hI(n)i2
'
K
1 X
Ik (n)2
K
k=1
!2
K
1 X
Ik (n)
K
k=1
Riportando su un grafico il valore di (n) per diversi valori di n `e possibile studiarne landamento allaumentare di n.
Il seguente programma calcola lerrore della stima dellintegrale valutato con il metodo Monte
Carlo che utilizza una distribuzione uniforme P (x) nellintervallo [a, b]. Il programma utilizza
la funzione integ mc() del modulo di integrazione numerica integ.c.
Programma: test-int mc.c
/*
* Descrizione : test precisione integrazione Monte Carlo
*
* $yaC - Primer : test - int_mc .c, v 1.0 26.02.2005 AC $
*/
# include < s t d l i b . h>
# include <e r r n o . h>
# include <s t d i o . h>
# include <math . h>
# include <s t d i o . h>
# include "integ .h"
/* funzione di prova */
static double f ( double ) ;
int main ( void )
{
int
sam , n_sam ;
int
n_pt , max_pt ;
double
value ;
double mean_1 , mean_2 ;
FILE fp ;
n_sam = 1 0 0 ;
max_pt = 1 0 0 0 0 ;
fp = fopen ( " int_mc .dat" , "w" ) ;
415
(Rev. 2.1)
if ( fp == NULL ) {
perror ( " Errore apertura file int_mc .dat " ) ;
exit ( errno ) ;
}
for ( n_pt = 1 0 ; n_pt < max_pt ; n_pt = 5 ) { /* ciclo # punti */
mean_1 = mean_2 = 0 . 0 ;
for ( sam = 0 ; sam < n_sam ; ++sam ) {
/* ciclo sequenze */
value = integ_mc ( 0 , 2 . 0 , n_pt , 0 , f ) ;
mean_1 += value ;
mean_2 += value value ;
}
mean_1 /= ( double ) n_sam ;
mean_2 = mean_2 / ( double ) n_sam mean_1 mean_1 ;
fprintf ( fp , "%d\t%f\t%f\t%f\n" ,
n_pt , mean_1 , mean_2 , sqrt ( mean_2 ) ) ;
}
return 0 ;
}
static double f ( double x )
{
return ( xx ) ;
}
Il ciclo pi`
u interno valuta lintegrale con n sam = K sequenze differenti di n pt = n punti e
calcola la media media 1 e la media quadratica mean 2 dei valori ottenuti. Osserviamo che la
funzione integ mc() viene chiamata con il seed uguale a 0 in modo da utilizzare ogni volta
una sequenza di numeri random diversi. I valori di media 1 e mean 2 sono scritti insieme al
numero n pt di punti utilizzati ed al valore della deviazione standard (n) sul file int mc.dat.
Il ciclo for pi`
u esterno ripete il calcolo per differenti valori di n pt.
Come esempio riportiamo i valori ottenuti per a = 0, b = 1, K = 100 e f (x) = x2
416
mean 1
mean 2
(n)
10
50
250
1250
6250
0.332493
0.335467
0.332024
0.333898
0.333878
0.007861
0.001998
0.000347
0.000069
0.000017
0.088660
0.044703
0.018622
0.008311
0.004164
(Rev. 2.1)
Se i valori di (n) sono riportati su un grafico in funzione del valore di 1/ n i dati cadono
su una retta
0.2
[0,1] f(x) = x
2
2
[0,2] f(x) = x
[0,1] f(x) = cos(x)
0.1
0.1
0.2
1/n
0.3
0.4
1/2
La pendenza della retta non `e universale e dipende sia dallintervallo di integrazione [a, b] che
dalla funzione f (x) come si chiaramente dalla figura dove sono riportati per confronto i valori
di (n) ottenuti sia variando lintervallo di integrazione, f (x) = x2 su [0, 2], che la funzione,
f (x) = cos(x) su [0, 1].
Indipendentemente dal valore della pendenza tuttavia i valori si allineano sempre su una
retta per cui ne segue che lerrore dellintegrazione con il metodo Monte Carlo decresce con
laumentare del numero di punti utilizzati come
(n) 1/ n
per n sufficientemente grande.
417
(Rev. 2.1)
(Rev. 2.1)
Nei problemi incontrati nello studio della fisica dei primi anni capita spesso di dover integrare
equazioni differenziali ordinarie del secondo ordine della forma:
d2 r
dr
+ a(t, r)
= b(t, r)
2
dt
dt
(3.2)
dr
dt = v(t)
dv
dt
d y 1 = f 1 (x, y 1 , . . . , y N )
dx
d 2
y
= f 2 (x, y 1 , . . . , y N )
dx
d y N = f N (x, y 1 , . . . , y N )
dx
Per una maggiore generalit`a la variabile indipendente `e stata indicata con x mentre le variabili
dipendenti con y i (i = 1, . . . , N ). Nel caso dellequazione (3.2) si ha quindi N = 2 ed
r(t) y 1 (x),
dr f 1 (x) = y 2 (x)
dt
v(t) y 2 (x),
Per poter determinare in maniera univoca la soluzione di unequazione differenziale non basta
conoscerne le equazioni ma `e anche necessario fornire alcune informazioni aggiuntive, chiamate
condizioni al bordo, specificando il valore delle variabili indipendenti y i (x) per alcuni valori
fissati della variabile indipendente x. In generale non `e richiesto che il valore di tutte le y i (x)
sia specificato lo stesso valore di x, tuttavia gli algoritmi numerici diventano in questo caso
418
(Rev. 2.1)
pi`
u complessi cosicche nel seguito si assumer`a sempre che il valore di tutte la variabili y i (x)
sia specificato per il valore x = x0
i = 1, . . . , N
y i (x)x=x0 = y i (x0 ) y0i ,
e si cercher`a la soluzione dellequazione differenziale per una valore x = xf o per valori dati
x xn = x0 + n h,
n = 0, 1, 2, . . .
della variabile indipendente. Questo `e quello che si chiama un problema ai valori iniziali e i
valori y0i sono chiamati condizione iniziale o condizione ad un punto.
A prescindere dalle condizioni al bordo utilizzate lidea di base di un qualsiasi algoritmo per
lintegrazione numerica di equazioni differenziali `e quella di considerare gli infinitesimi come
incrementi finiti e trasformare le equazioni differenziali in equazioni alle differenze finite che
forniscono la variazione delle variabili dipendenti quando la variabile indipendente `e variata
di un incremento finito h chiamato passo di integrazione. Teoricamente quanto pi`
u piccolo
`e il valore della discretizzazione h tanto migliore `e lapprossimazione della soluzione delle
equazioni differenziali fornita dalla soluzione delle equazioni alle differenze finite. Numericamente invece siccome la precisione del computer `e finita un valore di h troppo piccolo produce
una cattiva approssimazione della soluzione. La scelta del valore del passo di integrazione h
dipende in generale dallalgoritmo usato, dallequazione e dalla precisione del computer.
Lalgoritmo pi`
u semplice per lintegrazione di un sistema di equazioni differenziali ordinarie
del primo ordine quando sia nota la condizione iniziale a x = x0 `e lalgoritmo di Eulero:
i
yn+1
= yni + h f i (xn , yn1 , . . . , ynN ),
i = 1, 2, . . . , N
Questa formula pu`o essere ottenuta facilmente partendo dallo sviluppo di Taylor al primo
ordine:
d i
y (x) dx + O(dx2 )
y i (x + dx) = y i (x) +
dx
utilizzando lequazione differenziale (d/dx) y i (x) = f i (x, y 1 , . . . , y N ) e sostituendo a dx il
passo di integrazione h.
Lalgoritmo di Eulero `e un algoritmo del primo ordine perche come `e facile vedere dallo
sviluppo di Taylor la sua accuratezza, ossia la differenza tra la soluzione esatta e quella
ottenuta con lalgoritmo, `e O(h2 ). Chiaramente lalgoritmo di Eulero `e esatto nel caso in cui
tutte le y i (x) siano lineari in x.
Lalgoritmo di Eulero `e molto semplice ma non `e consigliato per la soluzione pratica di
equazioni differenziali. Tra i suoi difetti principali vi `e il fatto che non `e molto preciso,
se confrontato con altri algoritmi pi`
u sofisticati che usano lo stesso passo di integrazione h.
Inoltre se le funzioni f i (x, y 1 , . . . , y N ) non sono sufficientemente regolari lo schema di Eulero
pu`o risultare instabile nel senso che un piccolo errore dovuto ad esempio alla precisione finita
del computer pu`o crescere fino a dominare completamente la soluzione numerica. A discapito
di queste limitazioni tuttavia lalgoritmo di Eulero `e molto importante perche in un modo o
nellaltro `e alla base di molti algoritmi di integrazione pi`
u sofisticati.
Non `e difficile determinare la condizione di stabilit`a per lalgoritmo di Eulero. Limitandosi
infatti per semplicit`a al caso unidimensionale N = 1 ed assumendo che per x = xn la soluzione
419
(Rev. 2.1.1)
differisca dal valore esatto yn di una quantit`a yn , il valore della soluzione per x = xn+1 sar`
a
dato da
yn+1 + yn+1 = yn + yn + h f (xn , yn + yn )
dove yn+1 `e il valore che si avrebbe in assenza di errori e yn+1 lerrore sulla soluzione per
x = xn+1 . Se lerrore yn `e piccolo `e possibile sviluppare f (xn , yn + yn ) in potenze di yn
utilizzando uno sviluppo di Taylor, per cui tenendo i termini fino al primo ordine in yn
incluso si ha
yn+1 + yn+1 = yn + yn + h f (xn , yn ) + h
f (xn , yn ) yn + O (yn )2
yn
yn+1 = 1 + h
f (xn , yn ) yn
y
= gn yn .
Da questa equazione segue che lerrore cresce come il prodotto delle gn valutate lungo la
traiettoria
yn+1 = gn gn1 g2 g1 y0
dove y0 `e lerrore per x = x0 . Di conseguenza lalgoritmo di Eulero `e stabile solo se |gn | < 1,
ossia
f (xn , yn ) < 1.
1 < 1 + h
yn
La disuguaglianza a destra implica che (/yn ) f (xn , yn ) deve essere negativa, mentre da
quella a sinistra si ha la condizione di stabilit`a
1
h<2
f (xn , yn )
yn
Ad esempio se f (x, y) = a y il passo di integrazione h deve soddisfare la disuguaglianza
h < 2/|a| affinche lalgoritmo sia stabile. Nel caso generale il valore di gn non `e costante per
cui una condizione sufficiente per la stabilit`a dellalgoritmo `e che la condizione |gn | < 1 sia
verificata per tutti i punti (xn , yn ), n = 1, 2, 3 . . ., lungo la traiettoria.
420
(Rev. 2.1.1)
x = v0
Lequazione del moto contiene la grandezza che determina il periodo di oscillazione del
` infatti facile vedere che riscalando la
pendolo, il suo valore fissa quindi la scala del tempo. E
variabile t come
tt
lequazione del moto diventa
x
= sin(x)
dove per`o il tempo `e adesso misurato in unit`a di 1 . Lequazione del moto sar`a integrata in
questa forma.
Per utilizzare lalgoritmo di Eulero `e necessario trasformare lequazione del moto in un sistema di equazioni differenziali ordinarie del primo ordine. A tal fine si introduce la variabile
ausiliaria velocit`a
v(t) = x(t)
cosicche lequazione del moto del pendolo `e equivalente al sistema di due equazioni differenziali
ordinarie del primo ordine:
x = v
v = sin(x)
che possono essere facilmente integrate numericamente utilizzando lalgoritmo di Eulero.
Programma: pendulum-eu.c
/*
* Descrizione : integra lequazione del moto di un pendolo
*
con omega = 1.
*
Algoritmo di Eulero .
*
Usa pendulum -io.c per Input/ Output
*
* Input
: angolo iniziale in radianti
*
velocita iniziale
*
passo di integrazione
*
numero passi di integrazione
*
* Output
: su file con formato :
*
tempo x v energia
*
* $yaC - Primer : pendulum -eu.c v 2.1 02.03.05 AC $
*/
# include <math . h>
# include "pendulum -io.h"
/* Prototipi */
static void
euler ( double h , double x , double v ) ;
static double energy ( double x , double v ) ;
static double x_dot ( double x , double v ) ;
static double v_dot ( double x , double v ) ;
421
/*
/*
/*
/*
/*
(Rev. 2.1.1)
*/
*/
*/
*/
*/
FILE outf ;
/* Lettura dati iniziali */
read_init(&x_stp , &v_stp , &dt , &stp_max ) ;
/* File di output */
sprintf ( file_output , "angolo -x_ %0.2f-v_ %0.2f.dat" ,
x_stp , v_stp ) ;
printf ( "File Output : %s\n\n" , file_output ) ;
outf = open_file ( file_output , "w" ) ;
/* Valori iniziali */
write_out ( outf , 0 . 0 , x_stp , v_stp , energy ( x_stp , v_stp ) ) ;
/* Integrazione */
for ( stp = 0 ; stp < stp_max ; ++stp ) {
euler ( dt , &x_stp , &v_stp ) ;
write_out ( outf , dt ( double ) stp , x_stp , v_stp ,
energy ( x_stp , v_stp ) ) ;
}
fclose ( outf ) ;
return 0 ;
}
/* ---* x_dot ()
*/
double x_dot ( double x , double v )
{
return v ;
}
/* ---* v_dot ()
*/
double v_dot ( double x , double v )
{
return (sin ( x ) ) ;
}
/* ----
422
(Rev. 2.1.1)
* energy ()
*/
double energy ( double x , double v )
{
double e ;
e = 0 . 5 v v cos ( x ) ;
return e ;
}
/* ---* euler ()
*/
void euler ( double h , double x , double v )
{
double x_tmp , v_tmp ;
/* variabili di appoggio */
x_tmp = x ;
v_tmp = v ;
x = x_tmp + h x_dot ( x_tmp , v_tmp ) ;
v = v_tmp + h v_dot ( x_tmp , v_tmp ) ;
return ;
}
Per avere una maggiore flessibilit`a le operazioni di Input/Output sono gestite da funzioni del
modulo pendulum-io.c.
Note sul programma: pendulum-eu.c
read_init(&x_stp , &v_stp , &dt , &stp_max ) ;
La lettura dei valori iniziali `e fatta attraverso la funzione read init() del modulo
pendulum-io.c. Questo permette di cambiare alloccorrenza linput del programma
senza dover modificare il file sorgente del programma.
sprintf ( file_output , "angolo -x_ %0.2f-v_ %0.2f.dat" ,
x_stp , v_stp ) ;
dove X.XX e Y.YY sono i valori iniziali x0 e v0 di x stp e v stp. Questo `e ottenuto
mediante la funzione sprintf() per scrivere sulla stringa file output il nome del file
di output costruito con la stringa "angolo-x " ed i valori iniziali di x stp e v stp forniti
al programma. Per limitare la lunghezza del nome del file ed evitare la presenza di spazi
423
(Rev. 2.1.1)
bianchi i valori vengono scritti con il formato %0.2f, ossia con solo due cifre decimali
e 0-padding.
outf = open_file ( file_output , "w" ) ;
La funzione open file() del modulo pendulum-io.c sostituisce la funzione fopen().
La funzione open file() ha la stessa funzionalit`a della funzione fopen() ma effettua
il controllo sullapertura del file.
euler ( dt , &x_stp , &v_stp ) ;
La funzione euler() fa un passo di integrazione con lalgoritmo di Eulero. Le variabili
x stp e v stp contengono in ingresso il valore di x e v al tempo t e ed in uscita il loro
valore al tempo t + dt. La funzione non ritorna nulla.
write_out ( outf , dt ( double ) stp , x_stp , v_stp ,
energy ( x_stp , v_stp ) ) ;
Per avere una maggiore flessibilit`a sul formato di output questo viene gestito dalla
funzione write out() del modulo pendulum-io.c che prende come argomenti il puntatore outf allo stream di output ed i valori delle grandezza da scrivere sullo stream.
Per controllare laccuratezza dellintegrazione viene scritto anche il valore istantaneo
dellenergia meccanica totale
x 2
E=
cos(x)
2
dato dalla funzione energy() che prende come agromenti il valore di x spt e v stp
e restituisce il valore dellenergia meccanica corrispondente. Lenergia meccanica una
costante del moto per cui il suo valore deve rimanere costante, entro i limiti dellaccuratezza numerica.
x_tmp = x ;
v_tmp = v ;
x = x_tmp + h x_dot ( x_tmp , v_tmp ) ;
v = v_tmp + h v_dot ( x_tmp , v_tmp ) ;
/* ERRATO !!! */
/* ERRATO !!! */
`e errato perche per calcolare il nuovo valore di *v si `e usato il nuovo valore di *x e non
il vecchio.
Per realizzare lavanzamento in parallelo delle due variabili sarebbe sufficiente una
sola variabile di appoggio,
x_tmp = x ;
x = x_tmp + h x_dot ( x_tmp , v ) ;
v = v
+ h v_dot ( x_tmp , v ) ;
424
(Rev. 2.1.1)
Modulo pendumul-io.c
File: pendulum-io.c
/*
* Descrizione : modulo Input/ Output per i programmi
*
pendulum -ec.c e pendulum -vr.c
*
* Funzioni : void read_init ( double *x, double *v, double *h,
*
int *sm );
*
void write_out (FILE *pf , double t, double x, double v,
*
double e);
*
FILE * open_file (const char *path , const char *mode)
*
* $yaC - Primer : pendulum -io.c v 2.1 02.03.05 AC $
*/
# include < s t d l i b . h>
# include <e r r n o . h>
# ifndef PENDIO H
# include "pendulum -io.h"
# endif
/* ---* read_init ()
*/
extern void read_init ( double x , double v , double h , int sm )
{
char line [ 4 0 ] ;
/* buffer */
printf ( "\n" ) ;
printf ( " Angolo Iniziale ( radianti ): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , x ) ;
printf ( " Velocita Iniziale
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , v ) ;
printf ( " passo integrazone (dt)
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , h ) ;
printf ( "# passi integrazione
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , sm ) ;
return ;
}
425
(Rev. 2.1.1)
/* ---* write_out ()
*/
extern void write_out ( FILE pf , double t , double x , double v , double e )
{
static short flg = 1 ;
/* Se prima volta scrivi un header sul file */
if ( flg ) {
flg = 0 ;
fprintf ( pf , "# t\tx(t)\tv(t)\te(t)\n" ) ;
}
fprintf ( pf , "%.4f\t%.4f\t%.4f\t%.4f\n" ,
t, x, v, e);
return ;
}
/* ---* open_file ()
*/
extern FILE open_file ( const char path , const char mode )
{
FILE fa ;
if ( ( fa = fopen ( path , mode ) ) == NULL ) {
perror ( " Errore in apertura file output " ) ;
exit ( errno ) ;
}
return fa ;
}
legge dallo stdio i valori iniziali delle variabili x e v, il valore del passo di integrazione h ed il
numero massimo sm di passi di integrazione da effettuare. I suoi argomenti sono i puntatori
alle variabili corrispondenti. La funzione non restituisce valore.
Funzione write out()
La funzione
void write_out ( FILE pf , double t , double x , double v ,
double e ) ;
426
(Rev. 2.1.1)
scrive sullo stream di output pf il valore delle grandezze di interesse: il tempo t, la posizione
x, la velocit`a v e lenergia meccanica e. La funzione non restituisce valore.
La la prima volta che viene chiamata la funzione write out() scrive unintestazione nel file
di output. Questo `e ottenuto utilizzando la variabile locale flg di classe di memorizzazione
static
static short flg = 1 ;
/* Se prima volta scrivi un header sul file */
if ( flg ) {
flg = 0 ;
fprintf ( pf , "# t\tx(t)\tv(t)\te(t)\n" ) ;
}
La presenza di unintestazione permette di conoscere il contenuto delle diverse colonne del file
senza dover cercare nel file sorgente cosa viene scritto.
Funzione open file()
La funzione
FILE open_file ( const char path , const char mode ) ;
sostituisce la funzione fopen(). Per una maggiore portabilit`a prende gli stessi argomenti della
funzione fopen() e restituisce un puntatore di tipo FILE al file specificato da path. Diversamente dalla funzione fopen() se lapertura non pu`o essere effettuata la funzione open file()
produce un messaggio di errore ed interrompe lesecuzione del programma restituendo il codice
di errore contenuto nella variabile errno.
File di header: pendulum-io.h
/*
* Header file modulo pendulum -io.c
*
* Funzioni : void read_init ( double *x, double *v, double *h,
*
int *sm );
*
void write_out (FILE *pf , double t, double x, double v,
*
double e);
*
FILE * open_file (const char *path , const char *mode)
*
* Descrizione :
*
* read_init () -> Legge dallo stdio i valori della posizione x,
*
velocita v, passo di integrazione h e numero
*
massimo di passi di integrazione sm.
* write_out () -> Scrive sullo stream pf il valore del tempo t,
*
della posizione x, velocita v ed energia e.
* open_file () -> Apre lo stream di output individuato da path ,
*
nel modo mode controllando che non vi siano
*
errori di apertura . Restituisce il puntatore
*
allo stream . Se vi sono errori interrompe il
427
(Rev. 2.1.1)
*
programma con il codice di errore errno.
*
* $yaC - Primer : pendulum -io.h, v 2.1 02.02.05 AC $
*/
# include <s t d i o . h>
# ifndef PENDIO H
# define PENDIO H
# endif
extern void read_init ( double x , double v , double h , int sm ) ;
extern void write_out ( FILE pf , double t , double x , double v , double e ) ;
extern FILE open_file ( const char path , const char mode ) ;
Il file di header pendulum-io.h contiene oltre alle dichiarazioni delle funzioni la definizione
delle macro PENDIO H che pu`o essere utilizzata per evitare inclusioni multiple del file di
header. In questo caso il suo uso `e superfluo perche il file pendulum-eu.c utilizza oltre
al file pendulum-io.h solo file di header di sistema.
Il contenuto del file di output pu`o essere ispezionato con il comando UNIX more:
$ more angolox_0 .10 v_0 . 0 0 . dat
# t
x( t )
v( t )
e(t)
0 . 0 0 0 0 0 . 1 0 0 0 0 . 0 0 0 0 0.9950
0 . 0 0 0 0 0 . 1 0 0 0 0.0010 0.9950
0 . 0 1 0 0 0 . 1 0 0 0 0.0020 0.9950
0 . 0 2 0 0 0 . 1 0 0 0 0.0030 0.9950
...
Lalgoritmo di integrazione di Eulero `e molto poco accurato, come mostra la seguente figura
che riporta i valori di v in funzione di quelli di x ottenuti con un passo di integrazione dt = 0.01
e valori iniziali x0 = 0.1 e v0 = 0.
428
(Rev. 2.1)
0.2
0.1
-0.1
-0.2
-0.2
-0.1
0.1
0.2
Se lintegrazione fosse buona si dovrebbe avere una curva chiusa di equazione E = E(x0 , v0 )
perche lenergia si deve conservare. Il fatto che invece si abbia una spirale indica che lenergia
non si conserva, come pu`o essere verificato facilmente ispezionando il file di output.
In questi casi si deve diminuire il valore del passo di integrazione, o utilizzare un algoritmo
di integrazione migliore.
Esercizi
1. Riportare su un grafico la soluzione numerica ottenuta per diversi valori dellangolo
iniziale x0 e velocit`a iniziale v0 = 0 e la soluzione
x(t) = x0 cos(t)
valida per piccole oscillazioni. Studiare cosa succede allaumentare del valore di x0 .
y (x) = f i (y 1 , . . . , y N ) =
V (y 1 , . . . , y N ),
dx2
yi
i = 1, . . . , N
(3.3)
y (x0 ) =
y0i ,
d i
y (x)
= v0i .
dx
x=x0
(3.4)
429
(Rev. 2.1)
1X
E=
2
i=1
dy i
dx
2
+ V (y 1 , . . . , y N )
chiamata energia meccanica, come si pu`o verificare facilmente prendendo la derivata totale di
E rispetto a x ed usando lequazione (3.3):
dE
dx
N
N
i
X
dy i d2 y i X
1
N dy
+
V
(y
,
.
.
.
,
y
)
dx dx2
dx
i=1
i=1
i
N 2 i
X
d y
dy
1
N
=
+
V (y , . . . , y )
=0
dx2
yi
dx
i=1
Di conseguenza ogni algoritmo numerico utilizzato per integrare queste equazioni deve chiaramente conservare, entro i limiti numerici, il valore dellenergia E. Lalgoritmo di Eulero non
`e fra questi.
Un buon algoritmo di integrazione per equazioni della forma (3.3) `e dato dallalgoritmo di
Verlet:
i
i
yn+1
= 2 yni + h2 f i (yn1 , . . . , ynN ) yn1
,
i = 1, . . . , N
dove h `e il passo di integrazione:
x xn = x0 + n h,
n = 0, 1, 2, . . .
d i
h2 d 2 i
h3 d 3 i
y (x) +
y
(x)
+
y (x) + O(h4 ),
dx
2 dx2
6 dx3
y i (x h) = y i (x) h
d i
h2 d 2 i
h3 d 3 i
y (x) +
y
(x)
y (x) + O(h4 ).
dx
2 dx2
6 dx3
430
d2 i
y (x) + O(h4 )
dx2
(Rev. 2.1.1)
da cui utilizzando lequazione (3.3) per esprimere la derivata seconda di y i (x) e valutando il
tutto per x = xn segue facilmente lalgoritmo di Verlet.
Da questa derivazione segue anche che laccuratezza dellalgoritmo, ossia la differenza tra la
soluzione esatta e quella numerica, `e di ordine O(h4 ) nel passo di integrazione. Questo vuol
dire che se le y i (x) sono al massimo polinoni di grado 3 in x, allora lalgoritmo `e esatto. Per
questo motivo lalgoritmo `e detto del terzo ordine.
Nota. Lalgoritmo di Verlet utilizza il valore delle funzioni y i (x) in x = xn e x = xn1
per determinarne il nuovo valore in x = xn+1 . Di conseguenza questo algoritmo non pu`o
essere utilizzato con le condizioni iniziali (3.4) per determinare le y i (x1 ) in quanto manca il
` necessario quindi utilizzare un differente algoritmo di integrazione per
valore di y i (x0 h). E
i
determinare y (x1 ) a partire dalle condizioni iniziali date. Spesso con le condizioni iniziali
(3.4) si utilizza lalgoritmo di Eulero del secondo ordine:
h2 d 2 i
d i
y (x0 ) +
y (x0 ) + O(h3 )
dx
2 dx2
h2 i 1
= y0i + h v0i +
f (y0 , . . . , y0N ) + O(h3 )
2
y1i = y i (x0 ) + h
Sebbene questo algoritmo sia di ordine inferiore rispetto allalgoritmo di Verlet questo viene
usato per effettuare un solo passo di integrazione cosicche questa differenza `e generalmente
trascurabile.
cos(x)
x
x = v0
utilizzando lalgoritmo di Verlet. La struttura del programma `e simile a quella del programma
che utilizza lalgoritmo di Eulero, le differenze sono solo nella parte di integrazione.
Per determinare il valore di x(t + dt) con lalgoritmo di Verlet sono necessari sia il valore di
x(t) che quello x(t dt). Di conseguenza siccome allistante iniziale sono noti i valori di x(0) e
della sua derivata x(0)
431
(Rev. 2.1.1)
+
x
(0)
2
che fornisce il valore di x(dt) con un errore di ordine O(dt3 ).
Programma: pendulum-vr.c
/*
* Descrizione : integra lequazione del moto di un pendolo
*
con omega = 1.
*
Algoritmo di Verlet .
*
Usa pendulum -io.c per Input/ Output
*
* Input
: angolo iniziale in radianti
*
velocita iniziale
*
passo di integrazione
*
numero passi di integrazione
*
* Output
: su file con formato :
*
tempo x v energia
*
* $yaC - Primer : pendulum -vr.c v 2.1 02.03.05 AC $
*/
# include <math . h>
# include "pendulum -io.h"
/* Prototipi */
static double euler_2 ( double dt , double x , double v ,
double x_dd ( double x ) ) ;
static double verlet ( double dt , double x_c , double x_p ,
double x_dd ( double x ) ) ;
static double x_dotdot ( double x ) ;
static double energy ( double x , double v ) ;
int main ( void )
{
int
stp ;
int
stp_max ;
double x_0 , v_0 ;
double
x_new ;
double
x_cur ;
double
x_old ;
double
v_cur ;
double
dt ;
char file_output [ 4 0 ] ;
/*
/*
/*
/*
/*
/*
/*
/*
FILE outf ;
/* Lettura dati iniziali */
read_init(&x_0 , &v_0 , &dt , &stp_max ) ;
432
*/
*/
*/
*/
*/
*/
*/
*/
*/
(Rev. 2.1.1)
/* File di output */
sprintf ( file_output , "angolo -x_ %0.2f-v_ %0.2f.dat" , x_0 , v_0 ) ;
printf ( "File Output : %s\n\n" , file_output ) ;
outf = open_file ( file_output , "w" ) ;
/* Valori iniziali */
write_out ( outf , 0 . 0 , x_0 , v_0 , energy ( x_0 , v_0 ) ) ;
/* Primo passo con Eulero */
x_old = x_0 ;
x_cur = euler_2 ( dt , x_0 , v_0 , x_dotdot ) ;
v_cur = ( x_cur x_old ) / dt ;
/* stima velocita */
write_out ( outf , dt , x_cur , v_cur , energy ( x_cur , v_cur ) ) ;
/* Passi successivi con Verlet */
for ( stp = 2 ; stp < stp_max ; ++stp ) {
x_new = verlet ( dt , x_cur , x_old , x_dotdot ) ;
v_cur = 0 . 5 ( x_new x_old ) / dt ;
/* stima velocita */
x_old = x_cur ;
x_cur = x_new ;
write_out ( outf , dt ( double ) stp , x_cur , v_cur ,
energy ( x_cur , v_cur ) ) ;
}
fclose ( outf ) ;
return 0 ;
}
/* ---* x_dotdot ()
*/
double x_dotdot ( double x )
{
return(sin ( x ) ) ;
}
/* ---* energy ()
*/
double energy ( double x , double v )
{
double e ;
e = 0 . 5 v v cos ( x ) ;
return e ;
}
/* ---* euler_2 ()
433
(Rev. 2.1.1)
*/
double euler_2 ( double dt , double x_0 , double v_0 ,
double ( f ) ( double ) )
{
double x_1 ;
x_1 = x_0 + v_0 dt + 0 . 5 dt dt f ( x_0 ) ;
return x_1 ;
}
/* ---* verlet ()
*/
double verlet ( double dt , double x_t , double x_tm1 ,
double f ( double ) )
{
double x_tp1 ;
x_tp1 = ( ( double ) 2 . 0 ) x_t + dt dt f ( x_t ) x_tm1 ;
return x_tp1 ;
}
Per le operazioni di Input/Output il programma utilizza il modulo pendulum-io.c gi`a incontrato nellintegrazione dellequazione del moto del pendolo con lalgoritmo di Eulero.
Note sul programma: pendulum-vr.c
x_old = x_0 ;
x_cur = euler_2 ( dt , x_0 , v_0 , x_dotdot ) ;
v_cur = ( x_cur x_old ) / dt ;
/* stima velocita */
La funzione euler 2() restituisce il valore di x(dt) calcolato con lalgoritmo di Eulero del secondo ordine usando i valori iniziali x 0 e v 0. La velocit`a v cur = v(dt)
viene calcolata dal rapporto incrementale ed ha quindi una precisione inferiore, O(dt2 ),
rispetto a x cur. Tuttavia siccome la velocit`a viene usata solo per calcolare il valore dellenergia meccanica luso di una stima con precisione inferiore non modifica la
precisione dellagoritmo di integrazione.
x_new = verlet ( dt , x_cur , x_old , x_dotdot ) ;
v_cur = 0 . 5 ( x_new x_old ) / dt ;
x_old = x_cur ;
x_cur = x_new ;
/* stima velocita */
La funzione verlet() restituisce il valore di x(t + dt) calcolato con lalgoritmo di Verlet
a partire dal suo valore al tempo t contenuto nella variabile x cur e quello al tempo
t dt contenuto nella variabile x old. La velocit`a al tempo t, v cur, `e invece calcolata
434
(Rev. 2.0.4)
0.2
0.1
-0.1
-0.2
-0.2
-0.1
0.1
0.2
La differenza con il risultato ottenuto con lalgoritmo di Eulero per gli stessi valori del
passo di integrazione e condizione iniziale `e evidente. Lalgoritmo di Verlet, contrariamente
allalgoritmo di Eulero, produce una curva chiusa e quindi lenergia meccanica
x 2
cos(x)
2
`e conservata, entro i limiti della precisione numerica.
E=
Esercizi
1. Confrontare i risultati con quelli ottenuti con il programma che utilizza lalgoritmo di
Eulero per lintegrazione dellequazione del pendolo.
435
(Rev. 2.0.4)
(Rev. 2.0.4)
Vogliamo integrare numericamente lequazione del moto di due pianeti data dal sistema di
equazioni:
d2 x1
dt2
d 2 y1
m1 2
dt
d2 x2
m2 2
dt
d 2 y2
m2 2
dt
m1
x2 x1
|r1 r2 |3
y2 y1
= G m1 m2
|r1 r2 |3
x1 x2
= G m1 m2
|r1 r2 |3
y1 y2
= G m1 m2
|r1 r2 |3
= G m1 m2
y
m1
m2
Le masse dei pianeti mi , come la loro posizione e velocit`a iniziali sono conosciute, ma non
specificate.
Algoritmo di Integrazione
Come schema di integrazione usiamo lalgoritmo di Verlet che fornisce il valore della posizione
al tempo successivo t+dt quando sia nota la posizione al tempo t e al tempo precedente tdt.
436
(Rev. 2.0.4)
Schema di Flusso
Lo schema di flusso del programma pu`e essere riassunto come:
1. Leggi i dati iniziali;
2. Apri i files di output;
3. Scrivi la condizione iniziale;
4. Calcola la posizione al tempo dt usando Eulero;
5. Scrivi la posizione e velocit`a;
6. Calcola la posizione al tempo t + dt usando Verlet;
7. Scrivi la posizione e velocit`a;
8. Avanza il tempo t t + dt;
9. Se t < tim max ripeti 6 e 7;
10. Chiudi i files ed esci.
Programma: planets-ev.c
/* ***************************************************************
- Descrizione :
Calcola la traiettoria di due pianeti integrando
le equazioni del moto usando il metodo di
Verlet . Lintegrazione viene fatta nel sistema del
centro di massa.
Le condizioni iniziali mettono automaticamente il
centro di massa nell origine degli assi con
con velocita nulla.
Viene fatto un controllo sui dati iniziali per evitare
le collisioni .
Il programma controlla laccuratezza dell integrazione
dal energia meccanica .
- Input :
Massa pianeta 1
437
(Rev. 2.0.4)
Massa pianeta 2
posizione iniziale pianeta 1
velocita iniziale pianeta 1
numero di iterazioni
- Output :
- Parametri :
passo di integrazione DT
File pianeta 1 OUT_P1
File pianeta 2 OUT_P2
Costante di gravitazione G
parametro di collisione (in read_init ) MIN_COS
*/
*/
*/
*/
*/
*/
*/
/* ============================================================== */
/*
Definizioni di tipo di dati
*/
/* ============================================================== */
typedef struct {
double x ;
/* asse x */
double y ;
/* asse y */
} dvect_t ;
typedef struct {
double
mass ;
dvect_t
pos ;
dvect_t
vel ;
} planet_t ;
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
planet_t create_planet ( int n ) ;
void
eulero ( double dt , planet_t p ) ;
void
verlet ( double dt , planet_t p ) ;
void
read_init ( planet_t p , int t ) ;
void
write_out ( FILE pf , double t , planet_t p ) ;
double
energy ( planet_t p ) ;
438
FILE
(Rev. 2.0.4)
/* # passi di integrazione */
/* I due pianeti */
/* ****************************
* Leggi dati iniziali e dai *
* alcune informazioni utili *
**************************** */
read_init ( planet , &tim_max ) ;
printf ( "\ nIntegro per da 0 a %f\n" , dt ( double ) tim_max ) ;
outf_1 = open_file ( OUT_P1 , "w" ) ;
printf ( " Pianeta 1 -> : %s\n\n" , OUT_P1 ) ;
outf_2 = open_file ( OUT_P2 , "w" ) ;
printf ( " Pianeta 2 -> : %s\n\n" , OUT_P2 ) ;
write_out ( outf_1 , 0 . 0 , planet [ 0 ] ) ;
write_out ( outf_2 , 0 . 0 , planet [ 1 ] ) ;
printf ( "0: energy = %f\n" , energy ( planet ) ) ;
/* Primo passo con Eulero */
eulero ( dt , planet ) ;
/* scrivi sui files nuova posizione */
write_out ( outf_1 , 0 . 0 , planet [ 0 ] ) ;
write_out ( outf_2 , 0 . 0 , planet [ 1 ] ) ;
/* controllo energia */
printf ( "%f: energy = %f\n" , dt , energy ( planet ) ) ;
/* Integrazione con Verlet */
for ( tim = 2 ; tim <= tim_max ; ++tim ) {
verlet ( dt , planet ) ;
/* scrivi sui files nuova posizione */
write_out ( outf_1 , dt ( double ) tim , planet [ 0 ] ) ;
write_out ( outf_2 , dt ( double ) tim , planet [ 1 ] ) ;
/* Controllo integrazione con energia */
printf ( "%f: energy = %f\n" , dt ( double ) tim , energy ( planet ) ) ;
}
439
(Rev. 2.0.4)
fclose ( outf_1 ) ;
fclose ( outf_2 ) ;
return 0 ;
}
/* ============================================================== */
/*
Routines di definizione
*/
/* ============================================================== */
/* ---* create_planet ()
*
* Riserva lo spazio di memoria per n planet_t
*/
planet_t create_planet ( int n )
{
int
v;
planet_t c ;
c = ( planet_t ) malloc ( n sizeof ( planet_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Cannot allocate space for planet \n\n" ) ;
exit ( 1 ) ;
}
for ( v = 0 ; v < n ; ++v ) {
c [ v ] = ( planet_t ) malloc ( sizeof ( planet_t ) ) ;
if ( c [ v ] == NULL ) {
fprintf ( stderr , " Cannot allocate space for planet \n\n" ) ;
exit ( 1 ) ;
}
}
return c ;
}
/* ==============================================================
/*
Routines di I/O
/* ==============================================================
/* ---* read_init ()
*/
# define MIN COS 0 . 1
/* Parametri di collisione
/* macro privata della funz.
void read_init ( planet_t p , int tm )
{
double mod_r , mod_v , c_angle ;
char
line [ 4 1 ] ;
printf ( "\nI due pianeti partono in modo che: \n" ) ;
printf ( "*) Centro di massa nell origine (0 ,0)\n" ) ;
printf ( "*) Velocita entro di massa nulla (0 ,0)\n" ) ;
440
*/
*/
*/
*/
*/
(Rev. 2.0.4)
/* Pianeta 1 */
printf ( "\ nMassa pianeta 1: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p[0]> mass ) ) ;
printf ( " posizione pianeta 1 (x,y) : " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p[0]> pos . x ) , &(p[0]> pos . y ) ) ;
printf ( " velocita pianeta 1 (vx ,vy ): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p[0]> vel . x ) , &(p[0]> vel . y ) ) ;
/* Pianeta 2 */
printf ( "\ nMassa pianeta 2: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p[1]> mass ) ) ;
p[1]> pos . x = p[0]> mass p[0]> pos . x / p[1]> mass ;
p[1]> pos . y = p[0]> mass p[0]> pos . y / p[1]> mass ;
p[1]> vel . x = p[0]> mass p[0]> vel . x / p[1]> mass ;
p[1]> vel . y = p[0]> mass p[0]> vel . y / p[1]> mass ;
/* Controlla che i pianeti non collidano */
/* 1 - |cos (V_1 ,[ X_2 - X_1 ])| > MIN_COS */
mod_v = pow ( p[0]> vel . x p[0]> vel . x
+ p[0]> vel . y p[0]> vel . y , 0 . 5 ) ;
mod_r = pow ( p[0]> pos . x p[0]> pos . x
+ p[0]> pos . y p[0]> pos . y , 0 . 5 ) ;
c_angle = ( p[0]> pos . x p[0]> vel . x + p[0]> pos . y p[0]> vel . y ) /
( mod_v mod_r ) ;
if ( 1 fabs ( c_angle ) < MIN_COS ) {
fprintf ( stderr , "\n Collisione tra pianeti ! \n" ) ;
exit ( 1 ) ;
}
/* Scrivi dato iniziale */
printf ( "\ nPianeta 1:\n" ) ;
printf ( "mass: %f\n" ,
p[0]> mass ) ;
printf ( " posizione : (%f, %f)\n" , p[0]> pos . x , p[0]> pos . y ) ;
printf ( " velocit \a: (%f, %f)\n" , p[0]> vel . x , p[0]> vel . y ) ;
printf ( "\ nPianeta 2:\n" ) ;
printf ( "mass: %f\n" ,
p[1]> mass ) ;
printf ( " posizione : (%f, %f)\n" , p[1]> pos . x , p[1]> pos . y ) ;
printf ( " velocit \a: (%f, %f)\n" , p[1]> vel . x , p[1]> vel . y ) ;
printf ( "\n# iterazioni (dt = %f): " , DT ) ;
441
(Rev. 2.0.4)
scanf ( "%d" , tm ) ;
return ;
}
# undef MIN COS
/* ---* write_out ()
*/
void write_out ( FILE pf , double time , planet_t pln )
{
fprintf ( pf , "%f\t%f\t%f\t%f\t%f\n" , time , pln>pos . x , pln>pos . y ,
pln>vel . x , pln>vel . y ) ;
return ;
}
/* ---* open_file ()
*/
FILE open_file ( const char path , const char mode )
{
FILE fa ;
if ( ( fa = fopen ( path , mode ) ) == NULL )
{
fprintf ( stderr , "\n\ nErrore in apertura di %s\n" , path ) ;
exit ( EXIT_FAILURE ) ;
}
return fa ;
}
/* ============================================================== */
/*
Routines di Integrazione e Fisica
*/
/* ============================================================== */
/* ---* energy ()
*
* Calcola lenergia meccanica
*/
double energy ( planet_t p )
{
double
ene ;
double dist_x , dist_y , dist ;
dist_x = p[1]> pos . x p[0]> pos . x ;
dist_y = p[1]> pos . y p[0]> pos . y ;
dist
= pow ( dist_x dist_x + dist_y dist_y , 0 . 5 ) ;
ene
442
(Rev. 2.0.4)
443
(Rev. 2.0.4)
dt2 = 0 . 5 dt dt ;
/* Nuova posizione */
forza = compute_force ( plan ) ;
x_new [ 0 ] . x =
+
x_new [ 0 ] . y =
+
x_new [ 1 ] . x =
+
x_new [ 1 ] . y =
+
/* Nuova velocita */
plan [0]> vel . x = ( x_new [ 0 ] . x plan [0]> pos . x ) / dt ;
plan [0]> vel . y = ( x_new [ 0 ] . y plan [0]> pos . y ) / dt ;
plan [1]> vel . x = ( x_new [ 1 ] . x plan [1]> pos . x ) / dt ;
plan [1]> vel . y = ( x_new [ 1 ] . y plan [1]> pos . y ) / dt ;
/* Sposta i tempo avanti di dt */
x_old [ 0 ] . x
= plan [0]> pos . x ;
plan [0]> pos . x = x_new [ 0 ] . x ;
x_old [ 0 ] . y
= plan [0]> pos . y ;
plan [0]> pos . y = x_new [ 0 ] . y ;
x_old [ 1 ] . x
= plan [1]> pos . x ;
plan [1]> pos . x = x_new [ 1 ] . x ;
x_old [ 1 ] . y
= plan [1]> pos . y ;
plan [1]> pos . y = x_new [ 1 ] . y ;
free ( forza ) ;
forza = NULL ;
return ;
}
/* ---* verlet ()
*
* x(t+1) = 2 * x(t) + f(t) * dt^2 - x(t -1)
*/
void verlet ( double dt , planet_t plan )
{
double
dt2 ;
dvect_t x_new [ 2 ] ;
/* variabili temporanee */
dvect_t
forza ;
444
(Rev. 2.0.4)
/* Nuova posizione */
forza = compute_force ( plan ) ;
dt2 = dt dt ;
return ;
}
/*
/*
/*
/*
Intervallo di integrazione
out -file pianeta 1
out -file pianeta 2
Costante di Gravitazione
*/
*/
*/
*/
Usiamo delle macros per definire i parametri del programma. Siccome il valore della
costante di gravitazione fissa sostanzialmente la scala dei tempi possiamo prenderla
uguale a 1 ridefinendo opportunamente il tempo t.
445
(Rev. 2.0.4)
typedef struct {
double x ;
double y ;
} dvect_t ;
/* asse x */
/* asse y */
typedef struct {
double
mass ;
dvect_t
pos ;
dvect_t
vel ;
} planet_t ;
Prototipi delle funzioni usate nel programma. Osserviamo che la prima funzione ritorna
un puntatore ad un puntatore ad un oggetto di tipo planet t.
planet_t planet ;
/* I due pianeti */
/* allocazione statica */
ovvero
planet_t planet ;
/* allocazione dinamica */
446
(Rev. 2.0.4)
La lettura dei dati iniziali `e demandata ad una funzione in modo da rendere il programma pi`
u flessibile. La funzione prende come argomenti puntatori poich`e deve modificare il valore delle variabili.
outf_1 = open_file ( OUT_P1 , "w" ) ;
...
outf_2 = open_file ( OUT_P2 , "w" ) ;
Lapertura dei file di output viene effettuata dalla funzione open file() che controlla
eventuali problemi nellapertura dei files.
eulero ( dt , planet ) ;
Questa funzione prende la posizione e velocit`a attuale dei pianeti e ne calcola i valori
dopo un intervallo di tempo dt utilizzando lalgoritmo di Eulero del secondo ordine. I
nuovi valori sono riscritti sulla stessa variabile cosicch`e i vecchi valori sono persi.
for ( tim = 2 ; tim < tim_max ; ++tim ) {
verlet ( dt , planet ) ;
....
}
Questo `e il ciclo principale dellintegrazione. Ad ogni iterazione i pianeti vengono spostati per un tempo dt dalla funzione verlet() che utilizza lalgoritmo di Verlet.
La funzione verlet() prende gli stessi parametri della funzione eulero() in modo da
poterle scambiare facilmente e confrontare la precisione dei due algoritmi.
printf ( "%f: energy = %f\n" , dt ( double ) tim , energy ( planet ) ) ;
In questo sistema lenergia meccanica si conserva. Per controllare la precisione numerica
dellintegrazione viene calcolata e scritta sullo schermo lenergia meccanica istantanea.
c = ( planet_t ) malloc ( n sizeof ( planet_t ) ) ;
.....
c [ v ] = ( planet_t ) malloc ( sizeof ( planet_t ) ) ;
La variabile p `e un puntatore ad un array di puntatori a strutture. Siccome ogni elemento dellarray `e un puntatore ad una struttura per accedere ai campi della struttura
bisogna usare loperatore di selezione ->. Nel secondo caso il campo della struttura `e
a sua volta una struttura per cui per accedere ai suoi campi si deve usare loperatore di
selezione .. Volendo anche nel primo caso si poteva utilizzare loperatore di selezione
. in congiunzione con loperatore di dereferenza *. In questo caso per`o era necessario introdurre delle parentesi. Ad esempio al posto delle due istruzioni precedenti
avremmo dovuto utilizzare:
447
(Rev. 2.0.4)
Controlla che la condizione iniziale non sia tale da far collidere i due pianeti utilizzando
il parametro di collisione MIN COS. Siccome questo parametro ha scopo locale a questa
funzione il suo valore viene definito tramite una macro locale.
# define MIN COS 0 . 1
/* Parametri di collisione
*/
/* macro privata della funz. */
void read_init ( planet_t p , int tm )
{
....
}
# undef MIN COS
/* macro locale a questa funzione */
448
yaC-Primer: Runge-Kutta
(Rev. 2.0.1)
Lalgoritmo di Eulero utilizzato non fornisce la nuova velocit`a che viene quindi calcolata
dal rapporto incrementale
d
f (x + h) f (x)
f (x) =
+ O(h2 )
dx
h
dt2 = 2 . 0 dt ;
Lalgoritmo di Verlet utilizzato non fornisce la nuova velocit`a che quindi, come fatto per
lalgoritmo di Eulero, potrebbe essere calcolata dal rapporto incrementale. Tuttavia in
questo caso si conosce oltre alla posizione attuale e la prossima anche quella precedente
per cui si pu`o utilizzare la formula delle differenze centrali
d
f (x + h) f (x h)
f (x) =
+ O(h3 )
dx
2h
che fornisce una stima della derivata migliore del rapporto incrementale. Questa formula
pu`o facilmente essere ottenuta sottraendo lo sviluppo di Taylor di f (x h) da quello di
f (x + h).
Esercizi
1. Controllare il valore dellenergia meccanica in funzione del passo di integrazione;
2. Sostituire allalgoritmo di integrazione di Verlet quello di Eulero. A parit`a di passo di
integrazione, quale `e migliore?
3. Riscrivere il programma usando un array di oggetti di tipo planet t invece che un array
di puntatori ad oggetti di tipo planet t.
(3.5)
449
yaC-Primer: Runge-Kutta
(Rev. 2.0.1)
f = f 1, f 2, . . . , f N ,
(3.6)
Per integrare numericamente il sistema di equazioni differenziali (3.5) il primo passo consiste
nel discretizzare la variabile indipendente x in intervalli uguali di lunghezza h:
x xn = x0 + n h,
n = 0, 1, 2, . . .
Il passo successivo consiste nel trovare una soluzione approssimata di (3.5), con la condizione
iniziale (3.6), determinando il valore di y per i valori x = xn della variabile indipendente:
y(xn ) yn ,
n = 0, 1, 2, . . .
Lo schema pi`
u semplice di integrazione `e quello di Eulero:
i
yn+1
= yni + h f i (xn , yn ),
i = 1, 2, . . . , N
(Eulero)
Lalgoritmo di Eulero `e un algoritmo del primo ordine poich`e lerrore commesso `e di ordine
O(h2 ) nel passo di discretizzazione.
Lalgoritmo di Eulero in genere non `e molto preciso rispetto ad altri algoritmi che usano lo
stesso passo di integrazione h. Inoltre pu`o essere instabile se h `e troppo grande. Sebbene in
linea di principio lalgoritmo `e tanto pi`
u preciso quanto pi`
u `e piccolo h, il passo di integrazione
non pu`o di fatto essere preso arbitrariamente piccolo poich`e la precisione numerica su un
` quindi necessario sviluppare algoritmi di integrazione di ordine pi`
calcolatore `e finita. E
u alto
che diano una approssimazione migliore del valore di yn . nei punti x = xn .
k2i
i
yn+1
i = 1, 2, . . . , N
(3.7)
= yni + k2i
Lerrore commesso `e di ordine O(h3 ) nel passo di integrazione, e quindi di un ordine superiore
allalgoritmo di Eulero. Questo algoritmo `e quindi del secondo ordine per cui viene anche
chiamato Runge-Kutta del secondo ordine.
450
yaC-Primer: Runge-Kutta
(Rev. 2.0.1)
Il modo pi`
u semplice per mostrare che lerrore commesso `e di ordine O(h3 ) consiste nel
mostrare che lalgoritmo `e esatto per funzioni di grado fino al secondo, ossia per funzioni f al
massimo lineari nelle variabili. Alternativamente si pu`o usare lo sviluppo di Taylor. Iniziamo
integrando lequazione differenziale (3.5) tra tra x e x + h:
x+h
Z
y(x + h) y(x) =
ds f [s, y(s)]
(3.8)
Per sempicit`a consideriamo il caso N = 1, ossia il caso di una sola equazione differenziale ma
la dimostrazione pu`o facilmente essere estesa al caso di un sistema di equazioni differenziali
del primo ordine. Se h `e piccolo possiamo usare lo sviluppo di Taylor per calcolare f [s, y(s)]
nellintervallo di integrazione:
dy
f [s, y(s)] = f + fx + fy
(s x) + O (s x)2
dx
= f + [fx + fy f ] (s x) + O (s x)2
(3.9)
dove si `e usata la notazione
f := f [x, y(x)];
fx :=
f [x, y(x)];
x
fy :=
f [x, y(x)].
y
x+h
x+h
x+h
ds (s x) +
ds + [fx + fy f ]
x
ds O (s x)2
= h f + 12 h2 [fx + fy f ] + O(h3 )
(3.10)
451
(Rev. 2.0.2)
pi
u usato sfrutta le informazioni da quattro punti: linizio e la fine dellintervallo e due punti
intermedi. Lo schema dellalgoritmo `e:
i
k1
= h f i (xn , yn ),
= h f i (xn + 21 h, yn + 12 k1 ),
i = 1, 2, . . . , N
k2
k3i
= h f i (xn + 12 h, yn + 12 k2 ),
k4i
= h f i (xn + h, yn + k3 ),
i
yn+1 = yni + 61 k1i + 13 k2i + 13 k3i + 16 k4i
Questo algoritmo `e del quarto ordine in quanto lerrore commesso `e O(h5 ) per cui viene
usualmente chiamato Runge-Kutta del quarto ordine. La dimostrazione pu`o essere effettuata
seguendo le stessa procedura usata per lo schema del secondo ordine, ed `e lasciata come
esercizio.
(Rev. 2.0.2)
Questo programma estende il programma planets-rk.c al caso del moto di due pianeti in
presenza di un attrito viscoso, ossia proporzionale alla velocit`a.
In presenza di attrito viscoso le equazioni del moto di due pianeti sono date dal sistema di
equazioni:
d2 x1
dt2
d 2 y1
m1 2
dt
d2 x2
m2 2
dt
d 2 y2
m2 2
dt
m1
x2 x1
|r1 r2 |3
y2 y1
= G m1 m2
|r1 r2 |3
x1 x2
= G m1 m2
|r1 r2 |3
y1 y2
= G m1 m2
|r1 r2 |3
= G m1 m2
dx1
dt
dy1
dt
dx2
dt
dy2
dt
452
(Rev. 2.0.2)
/* ***************************************************************
- Descrizione :
Calcola la traiettoria di due pianeti integrando
le equazioni del moto usando Runge -Kutta del
secondo o quarto ordine . Nelle equazioni del moto e
presente un termine di attrito proporzionale alla
velocita del pianeta . Lintegrazione viene fatta
nel sistema del centro di massa.
Le condizioni iniziali mettono automaticamente il
centro di massa nell origine degli assi con velocita
nulla.
Viene fatto un controllo sui dati iniziali per evitare
le collisioni .
Calcolo dell energia meccanica e della dissipazione .
- Input :
Massa pianeta 1
Massa pianeta 2
posizione iniziale pianeta 1
velocita iniziale pianeta 1
numero di iterazioni
- Output :
- Parametri :
DT
OUT_P1
OUT_P2
G
N_ENE
MIN_COS
MIN_DIS
passo di integrazione
File pianeta 1
File pianeta 2
Costante di gravitazione
ogni quanti passi scrive lenergia
parametro di collisione (in read_init )
distanza minima tra i pianeti
453
(Rev. 2.0.2)
/*
Definizioni di tipo di dati
*/
/* ============================================================== */
typedef struct {
double x ;
/* asse x */
double y ;
/* asse y */
} dvect_ty ;
typedef struct {
double
mass ;
dvect_ty pos ;
dvect_ty vel ;
} planet_ty ;
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
planet_ty create_planet ( int n ) ;
void
rk2 ( double dt , planet_ty p ) ;
void
rk4 ( double dt , planet_ty p ) ;
void
read_init ( planet_ty p , int t ) ;
void
write_out ( FILE pf , double t , planet_ty pln ) ;
double
energy ( planet_ty p ) ;
double
d_energy ( planet_ty p ) ;
FILE
open_file ( const char path , const char mode ) ;
/* ============================================================== */
/*
variabili globali
*/
/* ============================================================== */
double Gamma ;
/* coefficente gamma globale per comodita */
int main ( void )
{
int
tim ;
int
tim_max ;
double
dt = DT ;
planet_ty planet ;
FILE
outf_1 ;
FILE
outf_2 ;
planet = create_planet ( 2 ) ;
/* # di passi di integrazione */
/* I due pianeti */
/* ***
* Leggi dati iniziali e dai
* alcune informazioni utili
*** */
read_init ( planet , &tim_max ) ;
printf ( "\ nIntegro per da 0 a %f\n" , dt ( double ) tim_max ) ;
outf_1 = open_file ( OUT_P1 , "w" ) ;
printf ( " Pianeta 1 -> : %s\n\n" , OUT_P1 ) ;
454
(Rev. 2.0.2)
455
(Rev. 2.0.2)
}
/* ============================================================== */
/*
Routines di I/O
*/
/* ============================================================== */
/* ---* read_init ()
*/
# define MIN COS 0 . 1
/* macro privata della funz. */
void read_init ( planet_ty p , int tm )
{
double mod_r , mod_v , c_angle ;
char
line [ 4 1 ] ;
printf ( "\nI due pianeti partono in modo che: \n" ) ;
printf ( "*) Centro di massa nell origine (0 ,0)\n" ) ;
printf ( "*) Velocita entro di massa nulla (0 ,0)\n" ) ;
/* Pianeta 1 */
printf ( "\ nMassa pianeta 1: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p [ 0 ] . mass ) ) ;
printf ( " posizione pianeta 1 (x,y) : " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p [ 0 ] . pos . x ) , &(p [ 0 ] . pos . y ) ) ;
printf ( " velocita pianeta 1 (vx ,vy): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p [ 0 ] . vel . x ) , &(p [ 0 ] . vel . y ) ) ;
printf ( "\ ncoeff attrito gamma: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &Gamma ) ;
/* Pianeta 2 */
printf ( "\ nMassa pianeta 2: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p [ 1 ] . mass ) ) ;
p [ 1 ] . pos . x = p [ 0 ] . mass p [ 0 ] . pos . x / p [ 1 ] . mass ;
p [ 1 ] . pos . y = p [ 0 ] . mass p [ 0 ] . pos . y / p [ 1 ] . mass ;
p [ 1 ] . vel . x = p [ 0 ] . mass p [ 0 ] . vel . x / p [ 1 ] . mass ;
p [ 1 ] . vel . y = p [ 0 ] . mass p [ 0 ] . vel . y / p [ 1 ] . mass ;
/* Controlla che i pianeti non collidano */
/* 1 - |cos (V_1 ,[ X_2 - X_1 ])| > MIN_COS */
mod_v = pow ( p [ 0 ] . vel . x p [ 0 ] . vel . x + p [ 0 ] . vel . y p [ 0 ] . vel . y , 0 . 5 ) ;
mod_r = pow ( p [ 0 ] . pos . x p [ 0 ] . pos . x + p [ 0 ] . pos . y p [ 0 ] . pos . y , 0 . 5 ) ;
456
(Rev. 2.0.2)
/* ---* write_out ()
*/
void write_out ( FILE pf , double time , planet_ty pln )
{
fprintf ( pf , "%f\t%f\t%f\t%f\t%f\n" , time , pln>pos . x , pln>pos . y ,
pln>vel . x , pln>vel . y ) ;
return ;
}
/* ---* open_file ()
*/
FILE open_file ( const char path , const char mode )
{
FILE fa ;
if ( ( fa = fopen ( path , mode ) ) == NULL )
{
fprintf ( stderr , "\n\ nErrore in apertura di %s\n" , path ) ;
exit ( EXIT_FAILURE ) ;
}
return fa ;
}
457
(Rev. 2.0.2)
/* ============================================================== */
/*
Routines di Integrazione e Fisica
*/
/* ============================================================== */
/* ---* energy ()
*
* Calcola lenergia meccanica
*/
double energy ( planet_ty p )
{
double
ene ;
double dist_x , dist_y , dist ;
dist_x = p [ 1 ] . pos . x p [ 0 ] . pos . x ;
dist_y = p [ 1 ] . pos . y p [ 0 ] . pos . y ;
dist
= pow ( dist_x dist_x + dist_y dist_y , 0 . 5 ) ;
ene
d_ene
( p ) [ 0 ] . vel . x +
( p ) [ 0 ] . vel . y +
( p ) [ 1 ] . vel . x +
( p ) [ 1 ] . vel . y ) ;
458
(Rev. 2.0.2)
/* ---* compute_deriv ()
*
* Calcola la derivata della posizione e velocita
*/
deriv_ty compute_deriv ( planet_ty p )
{
deriv_ty d ;
static double cut = MIN_DIS MIN_DIS MIN_DIS ;
double d_x , d_y , den ;
d = ( deriv_ty ) malloc ( 2 sizeof ( deriv_ty ) ) ;
if ( d == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory for derivatives \n" ) ;
exit ( 3 ) ;
}
d_x = p [ 1 ] . pos . x p [ 0 ] . pos . x ;
d_y = p [ 1 ] . pos . y p [ 0 ] . pos . y ;
den = pow ( d_x d_x + d_y d_y , 1 . 5 ) ;
if ( den < cut ) {
fprintf ( stdout , "\ nCollision ! my day .... my day ... \n\n" ) ;
exit ( 1 0 ) ;
}
d [ 0 ] . pos . x =
d [ 0 ] . pos . y =
d [ 0 ] . vel . x =
d [ 0 ] . vel . y =
p [ 0 ] . vel . x ;
p [ 0 ] . vel . y ;
( ( double ) G ) p [ 1 ] . mass d_x / den
Gamma p [ 0 ] . vel . x / p [ 0 ] . mass ;
( ( double ) G ) p [ 1 ] . mass d_y / den
Gamma p [ 0 ] . vel . y / p [ 0 ] . mass ;
d [ 1 ] . pos . x = p [ 1 ] . vel . x ;
d [ 1 ] . pos . y = p [ 1 ] . vel . y ;
d [ 1 ] . vel . x = (( double ) G ) p [ 0 ] . mass d_x / den
Gamma p [ 1 ] . vel . x / p [ 1 ] . mass ;
d [ 1 ] . vel . y = (( double ) G ) p [ 0 ] . mass d_y / den
Gamma p [ 1 ] . vel . y / p [ 1 ] . mass ;
return ( d ) ;
}
/*
*
*
*
*
*
*
*
*
---rk2 ()
Runge - Kutta 2nd order
\dot x = f(t,x)
k1 = f(t,x(t))
k2 = f(t + dt/2, x(t) + (dt /2)* k1)
x(t+1) = x(t) + dt * k2 + O(dt ^3)
459
(Rev. 2.0.2)
*/
void rk2 ( double dt , planet_ty plan )
{
register int
ip ;
double
dt2 ;
planet_ty
p_tmp ;
/* variabili temporanee */
deriv_ty
kappa ;
dt2 = 0 . 5 dt ;
p_tmp = create_planet ( 2 ) ;
/* k1 = f(t,x) */
kappa = compute_deriv ( plan ) ;
/* k2 = f(t+dt/2,x + dt*k1 /2) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . mass = plan [ ip ] . mass ;
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y
}
kappa = compute_deriv ( p_tmp ) ;
+
+
+
+
dt2
dt2
dt2
dt2
kappa [ ip ] . pos . x ;
kappa [ ip ] . pos . y ;
kappa [ ip ] . vel . x ;
kappa [ ip ] . vel . y ;
/* x(t+1) = x(t) + dt * k2 */
for ( ip = 0 ; ip < 2 ; ++ip ) {
plan [ ip ] . pos . x += dt kappa [ ip ] . pos . x ;
plan [ ip ] . pos . y += dt kappa [ ip ] . pos . y ;
plan [ ip ] . vel . x += dt kappa [ ip ] . vel . x ;
plan [ ip ] . vel . y += dt kappa [ ip ] . vel . y ;
}
/* Libera memoria temporanea */
free ( p_tmp ) ;
free ( kappa ) ;
return ;
}
/* ---* rk4 ()
*
* Runge - Kutta 4nd order
*
* \dot x = f(t,x)
* k0 = f(t,x(t))
* k1 = f(t + dt/2, x(t) + (dt /2)* k1)
* k2 = f(t + dt/2, x(t) + (dt /2)* k2)
* k3 = f(t + dt , x(t) + dt*k3)
* x(t+1) = x(t) + dt [k0/6 + k1/3 + k2/3 + k3 /6] + O(dt ^5)
*/
460
(Rev. 2.0.2)
/* Variabili temporanee */
p_tmp = create_planet ( 2 ) ;
/* k0 = f(t,x) */
k [ 0 ] = compute_deriv ( plan ) ;
/* k1 = f(t + dt/2, x(t) + (dt /2)* k0) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . mass = plan [ ip ] . mass ;
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x + dt2
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y + dt2
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x + dt2
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y + dt2
}
k [ 1 ] = compute_deriv ( p_tmp ) ;
/* k2 = f(t + dt/2, x(t) + (dt /2)* k1) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x + dt2
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y + dt2
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x + dt2
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y + dt2
}
k [ 2 ] = compute_deriv ( p_tmp ) ;
/* k3 = f(t + dt , x(t) + dt*k2) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y
}
k [ 3 ] = compute_deriv ( p_tmp ) ;
+
+
+
+
dt
dt
dt
dt
k [ 0 ] [ ip ] . pos . x ;
k [ 0 ] [ ip ] . pos . y ;
k [ 0 ] [ ip ] . vel . x ;
k [ 0 ] [ ip ] . vel . y ;
k [ 1 ] [ ip ] . pos . x ;
k [ 1 ] [ ip ] . pos . y ;
k [ 1 ] [ ip ] . vel . x ;
k [ 1 ] [ ip ] . vel . y ;
k [ 2 ] [ ip ] . pos . x ;
k [ 2 ] [ ip ] . pos . y ;
k [ 2 ] [ ip ] . vel . x ;
k [ 2 ] [ ip ] . vel . y ;
461
(Rev. 2.0.2)
+ 2 . 0 k [ 2 ] [ ip ] . vel . x +
k [ 3 ] [ ip ] . vel . x )
/ 6.0;
plan [ ip ] . vel . y += dt (
k [ 0 ] [ ip ] . vel . y + 2 . 0 k [ 1 ] [ ip ] . vel . y
+ 2 . 0 k [ 2 ] [ ip ] . vel . y +
k [ 3 ] [ ip ] . vel . y )
/ 6.0;
}
/* Libera memoria temporanea */
free ( p_tmp ) ;
for ( ip = 0 ; ip < 4 ; ++ip ) free ( k [ ip ] ) ;
return ;
}
10
*/
Per evitare di avere troppi numeri sullo schermo lenergia e la dissipazione di energia
sono scritte ogni N ENE passi di integrazione:
if ( ! ( tim % N_ENE ) ) {
printf ( "%f: energy = %f \t e_diss = %f\n" ,
energy ( planet ) ,
d_energy(&planet ) ) ;
}
dt ( double ) tim ,
*/
Per semplicit`a e non dover modificare troppo il programma planets-rk.c usiamo per
il coefficiente di attrito una variabile globale. Siccome le variabili globali sono viste
ovunque nel programma, per ricordarci che la variabile ha scopo globale, la chiamiamo
con la prima lettera maiuscola.
double d_energy ( planet_ty p )
{
double d_ene ;
462
= Gamma ( ( p ) [ 0 ] . vel . x
( p ) [ 0 ] . vel . y
( p ) [ 1 ] . vel . x
( p ) [ 1 ] . vel . y
return(d_ene ) ;
d_ene
(Rev. 2.0.2)
( p ) [ 0 ] . vel . x +
( p ) [ 0 ] . vel . y +
( p ) [ 1 ] . vel . x +
( p ) [ 1 ] . vel . y ) ;
Memoria
p
indirizzo *p
mass
(*(*p)).mass = (*p)[0].mass
= (*p)>mass
pos.x
(*(*p)).pos.x = (*p)[0].pos.x
= (*p)>pos.x
.........
vel.y
*p+1
mass
(*(*p+1)).mass = (*p)[1].mass
= (*p+1)>mass
pos.x
(*(*p+1)).pos.x = (*p)[1].pos.x
= (*p+1)>pos.x
.........
vel.y
463
(Rev. 2.0.2)
successivi sono necessarie in quanto **p+1 non ha lo stesso significato di *(*p+1). Infine
per accedere ai campi delle singole strutture si utilizza loperatore ., come mostrato
in figura.
Nel nel caso di un array di puntatori a strutture come quello usato nel programma
planets-ev.c lorganizzazione `e invece:
Memoria
p
indirizzo
indirizzo
Memoria
*p = p[0]
*(p+1) = p[1]
(**p).mass = (*(p[0])).mass
mass
pos.x
= p[0]>mass
(**p).pos.x = (*(p[0])).pos.x
= p[0]>pos.x
.........
vel.y
.........
(**(p+1)).mass = (*(p[1])).mass
mass
pos.x
= p[1]>mass
(**(p+1)).pos.x = (*(p[1])).pos.x
.........
= p[1]>pos.x
vel.y
( struct_ptr ) . struct_field
Per cui ad esempio nel caso in cui usiamo un array di strutture per la massa del secondo
pianeta abbiamo le scritture equivalenti:
( ( p + 1 ) ) . mass <==> ( p+1)>mass
464
(Rev. 2.0.4)
La variabile den contiene la distanza tra i pianeti al cubo. Per non dover ogni volta
calcolare una radice o una potenza, definiamo la variabile cut con classe static in
modo che la potenza venga calcolata una volta sola, la prima volta che la funzione viene
chiamata.
(Rev. 2.0.4)
/* ***************************************************************
- Descrizione :
Integrazione numerica dell equazione di un
oscillatore armonico forzato .
Integrazione : Eulero
(- DEULER )
Runge - Kutta 2nd (-DRK2)
Runge - Kutta 4th ( default )
Forzante : sinusoidale
- Input :
Massa oscillatore
Frequenza oscillatore
coefficiente di viscosita
posizione e velocita iniziali
parametri forza esterna
numero di iterazioni
- Output :
posizione , velocita , energia , dissipazione
potenza in funzione del tempo.
- Parametri :
465
(Rev. 2.0.4)
DT:
Passo di integrazione
OUT_F: File output
- $Id: forced_osc .c v 1.4 08.11.04 AC
**************************************************************** */
# include < s t d l i b . h>
# include <s t d i o . h>
# include <math . h>
/* ==============================================================
/*
Macros / Parametri
/* ==============================================================
# define DT
0.01
/* Intervallo di integrazione
# define OUT F "osci.dat"
/* out -file valori istantanei
*/
*/
*/
*/
*/
# define POW2( x ) ( ( x ) ( x ) )
/* ============================================================== */
/*
Definizioni di tipo di dati
*/
/* ============================================================== */
typedef struct {
double pos ;
/* x
*/
double vel ;
/* v = \dot x */
} snd_ord_t ;
/* ============================================================== */
/*
Prototipi funzioni
*/
/* ============================================================== */
snd_ord_t create_snd_ord ( void ) ;
void
oscillator_f ( double time , snd_ord_t point ,
snd_ord_t force ) ;
double
forcing ( double time ) ;
double
energy ( snd_ord_t point ) ;
double
d_energy ( double time , snd_ord_t point ) ;
double
power ( double time , snd_ord_t point ) ;
void
read_init ( snd_ord_t point , double time_max ) ;
void
eul ( double time , snd_ord_t point ,
void deriv ( double time , snd_ord_t point ,
snd_ord_t force ) ) ;
void
rk2 ( double time , snd_ord_t point ,
void deriv ( double time , snd_ord_t point ,
snd_ord_t force ) ) ;
void
rk4 ( double time , snd_ord_t point ,
void deriv ( double time , snd_ord_t point ,
snd_ord_t force ) ) ;
/* ============================================================== */
/*
variabili globali
*/
/* ============================================================== */
struct {
double
mass ;
/* massa dell oscillatore
*/
double
omega_0 ;
/* omega_0 dell oscillatore */
466
double
double
double
} Osc ;
gamma ;
f_0 ;
omega ;
/* attrito
/* ampiezza forcing
/* frequenza forcing
(Rev. 2.0.4)
*/
*/
*/
/* punto materiale */
/* ***********************************
* Legge i dati iniziali e
*
* scrive alcune informazioni utili *
*********************************** */
read_init ( point , &time_max ) ;
outf = fopen ( OUT_F , "w" ) ;
if ( outf == NULL ) {
fprintf ( stderr , "\n\ nErrore in apertura di %s\n" , OUT_F ) ;
return 1 ;
}
else {
printf ( " Output ->: %s\n\n" , OUT_F ) ;
}
fprintf ( outf , "# t \t x \t v \t e \t de \t pw\n" ) ;
time = 0 . ;
while ( time < time_max ) {
#if d e f i n e d (EULER)
eul(&time , point , oscillator_f ) ;
#elif d e f i n e d (RK2)
rk2(&time , point , oscillator_f ) ;
#else
rk4(&time , point , oscillator_f ) ;
# endif
fprintf ( outf , "%f\t%f\t%f\t%f\t%f\t%f\n" ,
time ,
point>pos ,
point>vel ,
energy ( point ) ,
d_energy ( time , point ) ,
power ( time , point ) ) ;
}
fclose ( outf ) ;
467
(Rev. 2.0.4)
return 0 ;
}
/* ==============================================================
/*
Routines Fisica
/*
/* oscillator_f () --> forza / massa
/* energy ()
--> energia meccanica
/* d_energy ()
--> dE/dt ( dissipazione )
/* forcing ()
--> forza esterna
/* power ()
--> potenza fornita dalla forza esterna
/* ==============================================================
/* ---* oscillator_f ()
*/
void oscillator_f ( double t , snd_ord_t var , snd_ord_t der )
{
der>pos = var>vel ;
der>vel = Osc . gamma var>vel
POW2 ( Osc . omega_0 ) var>pos
+ forcing ( t ) ;
return ;
}
/* ---* forcing ()
*/
double forcing ( double t )
{
return ( ( Osc . f_0 / Osc . mass ) sin ( Osc . omega t ) ) ;
}
/* ---* energy ()
*/
double energy ( snd_ord_t p )
{
double ene ;
ene = 0 . 5 Osc . mass POW2 ( p>vel )
+ 0 . 5 POW2 ( Osc . omega_0 ) POW2 ( p>pos ) ;
return ene ;
}
/* ---* d_energy ()
*/
double d_energy ( double t , snd_ord_t p )
{
double d_ene ;
468
*/
*/
*/
*/
*/
*/
*/
*/
*/
(Rev. 2.0.4)
: ");
: ");
: ");
: ");
469
(Rev. 2.0.4)
470
(Rev. 2.0.4)
+= dt ;
return ;
}
/* ********************************************
*
* Runge - Kutta 2nd order
*
* \dot x = f(t,x)
* k1 = f(t,x(t))
* k2 = f(t + dt/2, x(t) + (dt /2)* k1)
* x(t+1) = x(t) + dt * k2 + O(dt ^3)
*
* Input: time
*
point
*
funzione che calcola f
*
* Output : nuove coordinate point
*
nuovo tempo
*
******************************************** */
void rk2 ( double t , snd_ord_t p ,
void deriv ( double , snd_ord_t , snd_ord_t ) )
{
static double
dt = DT ;
static double
dt_2 = 0 . 5 ( double ) DT ;
static snd_ord_t
der ;
static snd_ord_t x_tmp ;
deriv ( t , p , &der ) ;
x_tmp . pos = p>pos + dt_2 der . pos ;
x_tmp . vel = p>vel + dt_2 der . vel ;
t += dt_2 ;
deriv ( t , &x_tmp , &der ) ;
p>pos += dt der . pos ;
p>vel += dt der . vel ;
t += dt_2 ;
return ;
}
/* ********************************************************
*
* Runge - Kutta 4nd order
*
* \dot x = f(t,x)
* k1 = f(t,x(t))
* k2 = f(t + dt/2, x(t) + (dt /2)* k1)
* k3 = f(t + dt/2, x(t) + (dt /2)* k2)
471
(Rev. 2.0.4)
472
(Rev. 2.0.1)
+ forcing ( t ) ;
struct {
double
double
double
double
double
} Osc ;
mass ;
omega_0 ;
gamma ;
f_0 ;
omega ;
/*
/*
/*
/*
/*
*/
*/
*/
*/
*/
La struttura Osc contiene tutti i dati relativi alloscillatore e viene dichiarata come
variabile globale per comodit`a. Per ricordarci che `e a scopo globale la chiamiamo con
la prima lettera maiuscola.
static double
dt = DT ;
static double
dt_2 = 0 . 5 ( double ) DT ;
static snd_ord_t
der ;
static snd_ord_t x_tmp ;
Si usano variabili con classe static in modo da riutilizzare sempre le stesse variabili.
Questo elimina i tempi di allocazione di memoria. Inoltre la moltiplicazione per 0.5
viene effettuata una volta sola e non ad ogni chiamata della funzione.
#if d e f i n e d (EULER)
eul(&time , point , oscillator_f ) ;
#elif d e f i n e d (RK2)
rk2(&time , point , oscillator_f ) ;
#else
rk4(&time , point , oscillator_f ) ;
# endif
Esercizi
1. Sostituire allattrito viscoso un attrito costante proporzionale al peso delloscillatore.
2. Modificare la legge di attrito viscoso in modo che sia proporzionale al cubo della velocit`a.
Lequazione adesso non `e pi`
u lineare. Studiare cosa succede con o senza forza esterna.
473
(Rev. 2.0.1)
Soluzione Analitica
La soluzione pi`
u generale dellequazione (3.11) `e:
x(t) = xom (t) + xp (t)
dove xom `e la soluzione dellequazione differenziale omogenea associata, e xp una soluzione
particolare della (3.11).
Lequazione differenziale `e a coefficienti costanti per cui la soluzione dellomogenea associata
`e:
xp (t) = e+ t + e t
dove e sono costanti che dipendono dalle condizioni iniziali, mentre sono le due
soluzioni di:
2 + + 02 = 0
Per una forzante sinusoidale la soluzione particolare `e della forma:
xp (t) = A sin(t + )
(3.12)
Se > 0 le parte reale di `e negativa per cui per tempi lunghi (t 1/ min[|+ |, | |] =
1/| |) la soluzione dellomogenea va a zero cosicch`e dopo un periodo transiente loscillatore
osciller`a con la stessa frequenza della forza esterna.
Per determinare la fase e lampiezza A sostituiamo la lespressione (3.12) nelequazione
(3.11):
F0
A(02 2 ) sin(t + ) + cos(t + ) =
sin(t)
m
Sviluppando cos(t + ) e sin(t + ), e raccogliendo a fattore otteniamo:
A(02 2 ) cos A sin ] sin(t)
F0
+ A(02 2 ) sin + A cos cos(t) =
sin(t)
m
Questa uguaglianza deve essere vera per ogni valore di t per cui deve essere
F0
m
2
2
A(0 ) sin + A cos = 0
A(02 2 ) cos A sin =
474
(Rev. 2.0.1)
2
0 2
A =
tg =
t+T
dtP (t)
t
Sostituendo lespressione trovata per P (t) e sviluppando cos(t+), ricordandosi che il valore
medio su un periodo di cos(t)2 `e 1/2, si ha
1
P = m 2 A()2
2
Casi Limite
0
= 0
0
F0 ,
A ' m
0
F
0
A ' m0 ,
A ' F0 2 ,
m
' 0.
' 2 .
' .
Soluzione Numerica
Lequazione differenziale (3.11) `e equivalente al sistema di equazioni differenziali del primo
ordine:
dx
dt
dv
dt
= fx (t) v(t)
= fv (t) v(t) 02 x(t) +
F0
sin(t)
m
(3.13)
475
(Rev. 2.0.1)
Per integrare numericamente queste equazioni possiamo usare sia lalgoritmo di Eulero che
quello di Runge-Kutta. Nel seguito confronteremo la loro precisione studiando il caso limite
delloscillatore libero.
Oscillatore Libero
m = 1, 0 = 3, = 0, f0 = 0, x(0) = 1, v(0) = 0
6
Eulero
Runge-Kutta 2
Runge-Kutta 4
-2
-4
-6
-2
-1
Si vede chiaramente che lalgoritmo di Eulero `e instabile, infatti nel piano x v la traiettoria
al posto di unellisse descrive una spirale.
Linstabilit`a risulta evidente anche dallo studio dellenergia meccanica che invece di essere
una costante cresce nel tempo.
476
(Rev. 2.0.1)
m = 1, 0 = 3, = 0, f0 = 0, x(0) = 1, v(0) = 0
12
Energia
10
Eulero
Runge-Kutta 2
Runge-Kutta 4
10
m = 1, 0 = 3, = 0, f0 = 0, x(0) = 1, v(0) = 0
4.501
4.5008
Runge-Kutta 2
Runge-Kutta 4
4.5006
Energia
4.5004
4.5002
4.5
0
10
477
(Rev. 2.0.1)
Oscillatore Smorzato
Consideriamo ora il caso di un oscillatore smorzato F0 = 0, con 0 = 3, = 1 e condizione
iniziale x(0) = 1 e v(0) = 0. Per lintegrazione usiamo lalgoritmo di Runge-Kutta del quarto
ordine. In questo caso loscillatore dopo un regime transiente si ferma, come si vede dalle
figure.
m = 1, 0 = 3, = 1, f0 = 0, x(0) = 1, v(0) = 0
1
exp(- t / 2)
0.5
-0.5
-1
10
m = 1, 0 = 3, = 1, f0 = 0, x(0) = 1, v(0) = 0
3
-1
-2
-3
-1
-0.5
0.5
478
(Rev. 2.0.1)
Oscillatore Forzato
Consideriamo infine il caso di un oscillatore forzato con F0 = 1, m = 1, 0 = 3, = 1 e
condizione iniziale x(0) = 1 e v(0) = 0. Lintegrazione viene effettuata con lalgoritmo di
Runge-Kutta del quarto ordine. In questo caso loscillatore dopo un transiente oscilla con la
frequenza della forza esterna.
m = 1, 0 = 3, = 1, f0 = 1, x(0) = 1, v(0) = 0
1
=2
=3
=4
0.5
-0.5
0
10
15
20
-1
-2
-3
-1
-0.5
0.5
479
(Rev. 2.0.1)
m = 1, 0 = 3, = 1, f0 = 1, x(0) = 1, v(0) = 1
Istantaneo
mediato
Energia
0.2
0.1
10
20
30
40
50
1.5
-Dissipazione media
Potenza media
0.5
-0.5
0
10
20
30
t/T
Oscillatore forzato: Potenza media e Dissipazione media (cambiata di segno) in funzione del ciclo. Dopo il
transiente iniziale i due valori coincidono.
Lampiezza delloscillazione `e massima per
q
M = 02 2 /2 < 0
Mentre la potenza media raggiunge il suo massimo per = 0 .
480
(Rev. 2.0)
m = 1, 0 = 3, = 1, f0 = 1
0.5
0.4
Potenza media
Ampiezza
0.3
0.2
0.1
Oscillatore forzato: Ampiezza e Potenza media in funzione della frequenza della forza esterna. Il massimo
dellampiezza `e a M < 0 , mentre quello della potenza
`e per 0 .
k 2
x + y 2 + 2hxy ,
2
1 h 1
n2 m2
,
n2 + m2
n, m = 1, 2, . . .
481
(Rev. 2.0)
tra un punto qualsiasi della traiettoria nello spazio delle fasi ed un punto che si muove su di
essa si azzera allora il moto `e periodico, quindi per determinare se il moto `e periodico basta
determinare se la traiettoria nello spazio delle fasi `e chiusa studiando la distanza tra i due
punti.
Per risolvere numericamente le equazioni del moto, e quindi determinare numericamente la
traiettoria nello spazio delle fasi, si discretizza sia lo spazio che il tempo cosicch`e la distanza
tra i due punti, quello di riferimento e quello che si muove sulla traiettoria, difficilmente
sar`a esattamente uguale a zero. Un modo semplice di determinare numericamente se la
traiettoria `e chiusa `e quindi quello di controllare se la distanza diventa pi`
u piccola di un valore
(cut off) piccolo ma finito prefissato. Questo valore rappresenta la precisione dellalgoritmo.
Chiaramente se il valore del cut off `e troppo grande traiettorie che passano abbastanza vicine
sono considerate periodiche anche se in realt`a non lo sono, daltro canto a causa dellerrore
numerico dellintegrazione se il valore del cut off `e troppo piccolo si rischia di non trovare
nessuna traiettoria periodica. Di conseguenza affinch`e il risultato sia affidabile `e necessario
fare uno studio con valori differenti del cut off.
Calcolo del periodo dellorbita periodica
Se lorbita `e periodica la distanza tra un punto qualsiasi sulla traiettoria nello spazio delle
fasi e un punto che si muove su di essa `e una funzione periodica del tempo con periodo uguale
a quello dellorbita. Ne segue che il periodo dellorbita `e quindi uguale allintervallo di tempo
tra due zeri successivi della distanza.
La distanza calcolata sulla traiettoria determinata numericamente difficilmente si azzera, per
cui il periodo deve essere determinato come lintervallo di tempo tra due valori minimi successivi della distanza. Questo pu`o essere fatto facilmente modificando lalgoritmo precedente per
il controllo della periodicit`a della traiettoria in modo che ogni volta che la distanza `e minore
del valore fissato del cut off si determini anche il valore minimo assunto dalla distanza ed il
tempo a cui questo occorre. Chiaramente gli intervalli di tempi tra due minimi successivi cos`
determinati non saranno identici, tuttavia se la traiettoria `e periodica il le fluttuazioni del
valore dovranno essere piccole rispetto allintervallo di integrazione scelto.
Questo algoritmo `e migliore del precedente, anche per la sola determinazione della periodicit`
a
o no della traiettoria, poich`e il cut off viene utilizzato solo per individuare i minimi diversi
e non per determinare se la traiettoria `e periodica. Questo viene fatto in modo pi`
u preciso
dallanalisi delle fluttuazioni degli intervalli di tempo tra due minimi successivi della distanza,
per cui non `e necessario fare uno studio variando il valore del cut off.
La funzione check period() del programma seguente calcola il periodo e le sue fluttuazioni
utilizzando questalgoritmo appena descritto.
Programma: two springs.c
/* ***************************************************************
- Descrizione : Integra le equazioni del moto di un punto
materiale di massa m soggetto ad una forza
centrale di potenziale :
482
(Rev. 2.0)
DT:
OUT_F:
CUT_OFF :
DIM:
Passo di integrazione
File output
Cutoff calcolo periodo .
Dimensioni dello spazio
/*
/*
/*
/*
Intervallo di integrazione
Out -File valori istantanei
Cut_off calcolo periodo
Dimensioni dello spazio
*/
*/
*/
*/
# define POW2( x ) ( ( x ) ( x ) )
/* Tipi */
typedef struct {
double pos ;
double vel ;
} point_t ;
/* Prototipi */
point_t point_new
void
point_copy
double
point_dist
void
deriv
double
energy
int
check_period
void
void
read_init
rk4
/* x
*/
/* v = \dot x */
( int n ) ;
( point_t scr , point_t dst , int n ) ;
( point_t p , point_t q , int n ) ;
( double t , point_t p , point_t d ) ;
( point_t p ) ;
( double t , double d , double prd_me ,
double prd_sq ) ;
( point_t p , double t ) ;
( double t , int n , point_t p ,
void der ( double t , point_t p , point_t d ) ) ;
483
(Rev. 2.0)
/* variabili globali */
struct {
double
m;
/* v(x,y) = (k/2) ( x^2 + y^2 + 2 h x y) */
double
k_m ;
/* :== k / m
*/
double
h;
} param ;
int main ( void )
{
int n_period ;
double time , time_max ;
double dist ;
double period_me , period_sq ;
point_t point_rt ;
point_t point_ref ;
FILE outf ;
point_rt = point_new ( DIM ) ;
point_ref = point_new ( DIM ) ;
/* ***
* Leggi dati iniziali e dai
* alcune informazioni utili
*** */
read_init ( point_rt , &time_max ) ;
printf ( "\n cut_off per periodo : %f\n\n" , CUT_OFF ) ;
/* posizione inizilale -> posizione di riferimento */
point_copy ( point_rt , point_ref , DIM ) ;
outf = open_file ( OUT_F , "w" ) ;
printf ( " Output ->: %s\n\n" , OUT_F ) ;
time
= 0.;
period_me = ( double ) 0 . 0 ;
period_sq = ( double ) 0 . 0 ;
while ( time <= time_max ) {
rk4(&time , DIM , point_rt , deriv ) ;
dist = point_dist ( point_rt , point_ref , DIM ) ;
n_period = check_period ( time , dist , &period_me , &period_sq ) ;
fprintf ( outf , "%f\t%f\t%f\t%f\t%f\t%f\t%f\n" ,
time ,
point_rt>pos [ 0 ] , point_rt>pos [ 1 ] ,
point_rt>vel [ 0 ] , point_rt>vel [ 1 ] ,
energy ( point_rt ) ,
dist
);
}
if ( n_period ) {
484
(Rev. 2.0)
*/
*/
*/
*/
*/
*/
d>pos [ 1 ] =
p>vel [ 1 ] ;
d>vel [ 1 ] = param . k_m ( p>pos [ 1 ] + param . h p>pos [ 0 ] ) ;
return ;
}
/* ---* energy ()
*/
double energy ( point_t p )
{
double ene ;
ene = POW2 ( p>vel [ 0 ] ) + POW2 ( p>vel [ 1 ] ) ;
ene += param . k_m ( POW2 ( p>pos [ 0 ] ) + POW2 ( p>pos [ 1 ] )
+ 2 . param . h p>pos [ 0 ] p>pos [ 1 ] ) ;
return ( ene = 0 . 5 param . m ) ;
}
/* ---* ckeck_period ()
485
(Rev. 2.0)
*
* ritorna il numero di periodi trovati
* o 0 se non trova nulla
*/
int check_period ( double time , double dist , double prd_me_p ,
double prd_sq_p )
{
static int
n_period = 0 ;
static int
found
= 1;
static double dist_min = 1 0 . CUT_OFF ;
static double time_pre = 0 . 0 ;
static double time_min = 0 . 0 ;
double d_time ;
if ( dist < CUT_OFF && ! found ) {
if ( dist < dist_min ) {
dist_min = dist ;
time_min = time ;
}
else {
++n_period ;
d_time
= time_min time_pre ;
prd_me_p += ( d_time
prd_me_p ) / ( double ) n_period ;
prd_sq_p += ( POW2 ( d_time ) prd_sq_p ) / ( double ) n_period ;
dist_min
= 1 0 . CUT_OFF ;
time_pre
= time_min ;
found
= 1;
}
}
else {
if ( dist > CUT_OFF && found ) found = 0 ;
}
return ( n_period ) ;
}
/* ============================================================== */
/*
Routines di I/O
*/
/* ============================================================== */
/* ---* read_init ()
*/
void read_init ( point_t p , double tm )
{
printf ( "\n Massa
: ");
scanf ( "%lf" , &(param . m ) ) ;
printf ( "\n k
scanf ( "%lf" , &(param . k_m ) ) ;
param . k_m /= param . m ;
486
: ");
printf ( "\n h
scanf ( "%lf" , &(param . h ) ) ;
(Rev. 2.0)
: ");
487
(Rev. 2.0)
/* ---* point_copy ()
*/
void point_copy ( point_t src , point_t dest , int n )
{
register int k ;
for ( k = 0 ; k < n ; ++k ) {
dest>pos [ k ] = src>pos [ k ] ;
dest>vel [ k ] = src>vel [ k ] ;
}
return ;
}
/* ---* point_dist ()
*/
double point_dist ( point_t p , point_t q , int n )
{
register int k ;
double d , dpos , dvel ;
d = 0;
for ( k
dpos
dvel
d +=
}
return
= 0; k < n ;
= p>pos [ k ]
= p>vel [ k ]
dpos dpos
++k ) {
q>pos [ k ] ;
q>vel [ k ] ;
+ dvel dvel ;
( sqrt ( d ) ) ;
}
/* ============================================================== */
/*
Routines di Integrazione
*/
/* ============================================================== */
/* ---* rk4 ()
*
* Runge - Kutta 4nd order
*
* \dot x = f(t,x)
* k1 = f(t,x(t))
* k2 = f(t + dt/2, x(t) + (dt /2)* k1)
* k3 = f(t + dt/2, x(t) + (dt /2)* k2)
* k4 = f(t + dt , x(t) + dt*k3)
* x(t+1) = x(t) + k1/6 + k2/3 + k3/3 + k4/6 + O(dt ^5)
*
*/
void rk4 ( double t , int n , point_t p ,
void deriv ( double , point_t , point_t ) )
{
488
(Rev. 2.0)
register int i ;
static double dt
= DT ;
static double dt_2 = 0 . 5 ( double ) DT ;
static point_t k1 , k2 , k3 , k4 ;
static point_t x_tmp ;
static int start = 1 ;
if ( start ) {
start = 0 ;
x_tmp = point_new ( n ) ;
k1
= point_new ( n ) ;
k2
= point_new ( n ) ;
k3
= point_new ( n ) ;
k4
= point_new ( n ) ;
}
deriv ( t , p , k1 ) ;
t += dt_2 ;
for ( i = 0 ; i < n ; ++i ) {
x_tmp>pos [ i ] = p>pos [ i ] + dt_2 k1>pos [ i ] ;
x_tmp>vel [ i ] = p>vel [ i ] + dt_2 k1>vel [ i ] ;
}
deriv ( t , x_tmp , k2 ) ;
for ( i = 0 ; i < n ; ++i ) {
x_tmp>pos [ i ] = p>pos [ i ] + dt_2 k2>pos [ i ] ;
x_tmp>vel [ i ] = p>vel [ i ] + dt_2 k2>vel [ i ] ;
}
deriv ( t , x_tmp , k3 ) ;
t += dt_2 ;
for ( i = 0 ; i < n ; ++i ) {
x_tmp>pos [ i ] = p>pos [ i ] + dt k3>pos [ i ] ;
x_tmp>vel [ i ] = p>vel [ i ] + dt k3>vel [ i ] ;
}
deriv ( t , x_tmp , k4 ) ;
for ( i = 0 ; i < n ; ++i ) {
p>pos [ i ] += dt (
k1>pos [ i ]
+ 2 . 0 k3>pos [ i ]
p>vel [ i ] += dt (
k1>vel [ i ]
+ 2 . 0 k3>vel [ i ]
}
+ 2 . 0 k2>pos [ i ]
+
k4>pos [ i ] ) / 6 . 0 ;
+ 2 . 0 k2>vel [ i ]
+
k4>vel [ i ] ) / 6 . 0 ;
return ;
}
489
(Rev. 2.0)
typedef struct {
double pos ;
double vel ;
} point_t ;
/* x
*/
/* v = \dot x */
point_copy
point_dist
( int n ) ;
( point_t scr , point_t dst , int n ) ;
( point_t p , point_t q , int n ) ;
m;
k_m ;
h;
Se lorbita `e periodica stampa il numero di periodi trovati, il periodo medio e la fluttuazione per controllare la precisione del periodo calcolato.
490
(Rev. 2.0)
found
*/
= 1;
La funzione usa CUT OFF per determinare se lorbita possa essere periodica, e la variabile
found per determinare se `e stato trovato un minimo della distanza.
if ( dist < CUT_OFF && ! found ) {
...
}
else {
if ( dist > CUT_OFF && found ) found = 0 ;
}
Se la distanza `e minore di CUT OFF e non `e stato trovato il minimo si cerca il minimo.
Se invece la distanza `e maggiore di CUT OFF ed `e stato trovato un minimo la traiettoria
si st`a allontanando da minimo, per cui si rimette il valore di found a 0 per cercare il
prossimo minimo.
static double time_pre
= 0.0;
La variabile time pre contiene il tempo a cui `e stato trovato il minimo precedente ed
`e utilizzata per calcolare lintervallo di tempo tra due minimi successivi della distanza.
Come tutte le altre variabili che devono conservare i valori da una chiamata allaltra
della funzione time pre viene dichiarata con classe static ed inizializzata con il valore
0.0 poich`e per costruzione allistante iniziale la distanza `e nulla.
Lo stesso discorso vale per le variabili n period, dist min e time min che contengono
rispettivamente il numero di periodi trovati, lultimo valore minimo della distanza
trovato ed il tempo a cui `e stato trovato. Siccome la distanza minima `e sicuramente non
pi`
u grande di CUT OFF la variabile dis min viene inizializzata con un valore pi`
u grande.
if ( dist < dist_min ) {
dist_min = dist ;
time_min = time ;
}
else {
++n_period ;
d_time
=
period_me +=
period_sq +=
dist_min
=
time_pre
=
found
=
}
time_min time_pre ;
( d_time
period_me ) / ( double ) n_period ;
( POW2 ( d_time ) period_sq ) / ( double ) n_period ;
1 0 . CUT_OFF ;
time_min ;
1;
Se la distanza dist `e minore dellultimo valore minimo trovato dist min allora si prende
il valore di dist come nuovo valore minimo e contemporaneamente si salva il tempo.
Se invece il valore della distanza dist `e maggiore dellultimo valore minimo trovato
dist min il punto si st`a allontanando dal punto di riferimento sulla traiettoria e quindi
491
(Rev. 2.0)
1
x hxiN 1 )
N
k 2
(x + y 2 + 2hxy)
2
sono
m
x = k (x + hy)
m
y = k (y + hx).
che sommate e sottratte forniscono il sistema di equazioni
2
x
+ y = +
(x + y)
2
x
y =
(x y)
2 = (k/m)(1 h). La soluzione di questo sistema di equazioni `
con
e:
492
(Rev. 2.0)
Soluzioni Periodiche
Affinch`e la soluzione sia periodica `e necessario che il rapporto tra le due frequenze sia un
numero razionale:
r
+
1+h
n
=
= ,
n, m = 1, 2, . . .
1h
m
Risolvendo per h si ha che la traiettoria `e periodica se `e soddisfatta la condizione:
h=
n2 m2
,
n2 + m2
n, m = 1, 2, 3, . . .
con
T =
2
= 2
m
k (1 h)
493
494
(Rev. 2.0)
4. Strutture Dati
4.1. Single Linked Lists
(Rev. 2.0.2)
dove
index
data
Le caratteristiche di una struttura dinamica dipendono molto dalla geometria delle connessioni
tra i vari nodi. La pi`
u semplice struttura dinamica `e una lista di elementi in cui ciascun
elemento punta al seguente, come in una caccia al tesoro. Questo tipo di strutture sono
chiamate Single Linked Lists (Liste con una connessione).
La struttura di un nodo di una single linked list `e della forma:
struct node {
struct node next ;
data_type
data ;
};
Il campo data della struttura node contiene il valore memorizzato nel nodo. Questo campo
dipende dal tipo di dati che si vogliono memorizzare.
Il campo next `e un puntatore ad una struttura dello stesso tipo node e serve per creare il
collegamento con il prossimo nodo della lista:
struct node node_1 ;
struct node node_2 ;
495
(Rev. 2.0.2)
node_2
next
next
data
data
NULL
Il campo next dellultimo nodo contiene il puntatore NULL per indicare la fine della lista.
La creazione di liste concatenate come nellesempio precedente `e applicabile solo quando
il numero dei nodi non `e troppo grande. Per poter utilizzare le liste in modo efficiente `e
necessario avere delle procedure che permettano di operare facilmente sulle liste, come ad
esempio per aggiungere/togliere un elemento dalla lista.
Le operazioni principali sulle liste sono:
Definizione della lista;
Creazione della lista;
Inserimento di un elemento;
Conteggio del numero di elementi;
Ricerca di un elemento;
Cancellazione di un elemento;
definisce un nodo di una single linked list che contiene un valore di un tipo int.
` possibile
Lutilizzo di typedef non `e necessario, ma rende la scrittura del codice pi`
u agevole. E
utilizzare typedef direttamente nella definizione della struttura:
typedef struct sgle_link {
struct sgle_link next ;
int
data ;
} sgle_link_t ;
496
(Rev. 2.0.2)
In questo caso non si pu`o per`o omettere il nome della struttura sgle link in quanto la
definizione del tipo struttura `e incompleta al momento della definizione dei suoi campi. Di
conseguenza non possiamo usare il tipo sgle link t nella definizione dei campi della struttura
sgle link poich`e questo viene definito solo una volta conclusa tutta la definizione della
struttura e quindi allinterno delle parentesi graffe {} sgle link t non `e ancora definito.
ovvero
sgle_link_t first = NULL ;
Quando la lista viene creata questa `e vuota, per cui first viene inizializzato a NULL.
Inserimento incondizionato
Gli elementi vengono aggiunti senza tener conto del loro contenuto ma in ordine di arrivo, ad
esempio, il nuovo elemento viene aggiunto allinizio della lista. La seguente funzione illustra
la procedura per la lista di tipo sgle link t.
Funzione: add sgl list()
void add_sgl_list ( sgle_link_t first , int item )
{
sgle_link_t new_entry ;
new_entry = ( sgle_link_t ) malloc ( sizeof ( sgle_link_t ) ) ;
if ( new_entry == NULL ) {
fprintf ( stderr , "\n Failed to allocate memory \n" ) ;
exit ( 1 ) ;
}
new_entry>data = item ;
new_entry>next = first ;
first = new_entry ;
return ;
}
La funzione deve poter modificare il valore di first, che contiene lindirizzo di memoria
(puntatore) del primo elemento della lista a cui aggiungere il nuovo elemento, di conseguenza
deve conoscere lindirizzo di memoria dove `e contenuto il valore di first. Siccome first `e
un puntatore la funzione prende quindi un parametro di tipo sgle link t **first p, ossia
un puntatore al puntatore della lista, ed uno di tipo int data, il valore da inserire.
497
(Rev. 2.0.2)
La procedura per aggiungere un nuovo elemento alla lista segue i seguenti passi:
Creazione di un nuovo elemento della lista:
sgle_link_t new_entry ;
new_entry = ( sgle_link_t ) malloc ( sizeof ( sgle_link_t ) ) ;
new_entry
*first
next
next
data
data
....
new_entry
*first
next
next
data
data
....
*first
next
next
data
data
....
498
(Rev. 2.0.2)
sgle_link_t add_ord_sgl_list ( sgle_link_t first , int item )
{
sgle_link_t new_entry ;
/* Nuovo elemento
*/
sgle_link_t prev_entry ;
/* Elemento precedente */
sgle_link_t next_entry ;
/* Elemento seguente
*/
499
(Rev. 2.0.2)
new_entry>next = next_entry ;
prev_entry>next = new_entry ;
return first ;
}
Dopo aver creato un nuovo elemento della lista, ed avergli assegnato il valore,
sgle_link_t new_entry ;
/* Nuovo elemento
*/
...
new_entry = ( sgle_link_t ) malloc ( sizeof ( sgle_link_t ) ) ;
...
new_entry>data = item ;
la procedura per inserire il nuovo elemento nella lista segue i seguenti passi:
Per prima cosa si controlla se la lista `e vuota oppure se il nuovo elemento `e pi`
u piccolo
di quelli gi`a presenti nella lista. In entrambi i casi il nuovo elemento viene inserito
allinizio della lista:
if ( ( first == NULL ) | | ( item <= first>data ) ) {
new_entry>next = first ;
first
= new_entry ;
return first ;
}
Osserviamo che si deve controllare prima se la lista `e vuota e solo nel caso che questa
non lo sia effettuare il controllo con il primo elemento della lista che, essendo la lista
ordinata, `e lelemento con il valore pi`
u piccolo contenuto nella lista. Di conseguenza
lordine con cui le due espressioni logiche sono scritte nel test logico `e rilevante. Se
avessimo scritto
if ( ( item <= first>data ) | | ( first == NULL ) )
/* ERRATO */
500
next_entry
next
next
data
new_entry
(Rev. 2.0.2)
data
next
data
Per terminare il ciclo quando si `e raggiunto il punto desiderato si usa listruzione break
che termina immediatamente il ciclo while. Se la condizione non `e mai soddisfatta il
ciclo termina quando si raggiunge la fine della lista e lelemento verr`a aggiunto in coda.
Il nuovo elemento viene inserito tra i due elementi identificati dai due puntatori ausiliari.
new_entry>next = next_entry ;
prev_entry>next = new_entry ;
prev_entry
next_entry
next
next
data
new_entry
data
next
data
501
(Rev. 2.0.2)
int cnt_sgl_list ( sgle_link_t first )
{
int cnt ;
void
add_sgl_list ( sgle_link_t first , int item ) ;
sgle_link_t add_ord_sgl_list ( sgle_link_t first , int item ) ;
int
cnt_sgl_list ( sgle_link_t first ) ;
int main ( void )
{
int i , data ;
sgle_link_t ord_list = NULL ;
sgle_link_t
list = NULL ;
sgle_link_t
list_1 , list_2 ;
srand ( 1 2 3 4 5 ) ;
printf ( " elementi iniziali nelle liste: non -ord = %d ord = %d\n" ,
cnt_sgl_list ( list ) ,
cnt_sgl_list ( ord_list ) ) ;
502
(Rev. 2.0.2)
Per semplicit`a le funzioni sono incluse nel programma utilizzando il comando #include del
preprocessore C. Questa procedura `e giustificata in questo caso dal fatto che il programma ha
il solo scopo di provare le funzioni. In genere tuttavia questo uso del comando #include `e
considerato una cattiva programmazione perch`e rende il programma di difficile lettura.
Quando il programma viene eseguito sullo schermo si ottiene:
elementi iniziali nelle liste : nonord = 0 ord = 0
Dati
input
input
input
input
input
input
input
input
immessi :
data [ 0 ] :
data [ 1 ] :
data [ 2 ] :
data [ 3 ] :
data [ 4 ] :
data [ 5 ] :
data [ 6 ] :
data [ 7 ] :
17
39
16
21
6
5
27
4
>
>
>
>
>
>
>
>
#
#
#
#
#
#
#
#
elementi
elementi
elementi
elementi
elementi
elementi
elementi
elementi
lista :
lista :
lista :
lista :
lista :
lista :
lista :
lista :
nonord
nonord
nonord
nonord
nonord
nonord
nonord
nonord
=
=
=
=
=
=
=
=
1
2
3
4
5
6
7
8
ord
ord
ord
ord
ord
ord
ord
ord
=
=
=
=
=
=
=
=
1
2
3
4
5
6
7
8
503
input data [ 8 ] :
input data [ 9 ] :
(Rev. 2.0.2)
=
=
=
=
=
=
=
=
=
=
27
32
4
27
5
6
21
16
39
17
Ordinata
ord_list [ 0 ]
ord_list [ 1 ]
ord_list [ 2 ]
ord_list [ 3 ]
ord_list [ 4 ]
ord_list [ 5 ]
ord_list [ 6 ]
ord_list [ 7 ]
ord_list [ 8 ]
ord_list [ 9 ]
=
=
=
=
=
=
=
=
=
=
4
5
6
16
17
21
27
27
32
39
La lista non ordinata contiene i dati nellordine inverso a quello con cui sono stati immessi
perch`e i dati sono aggiunti allinizio della lista per cui lultimo dato inserito `e il primo della
lista.
Ricerca di un elemento
Per sapere se un elemento `e contenuto nella lista basta scorrere la lista fino a che non si
trova lelemento voluto ovvero si raggiunge la fine della lista. La seguente funzione effettua
la ricerca di un valore in una lista di tipo sgle link t.
Funzione: find el sgl list()
int find_el_sgl_list ( sgle_link_t first , int item )
{
sgle_link_t curr_entry ;
curr_entry = first ;
*/
*/
curr_entry = curr_entry>next ;
}
return ( curr_entry == NULL ) ;
}
La parte principale della funzione `e costituita dal ciclo while che confronta successivamente
tutti gli elementi della lista con lelemento cercato. Il ciclo si interrompe se:
Si raggiunge la fine della lista. In questo caso curr entry `e uguale a NULL e la funzione
504
yaC-Primer: Stack
(Rev. 2.0.4)
ritorna il valore 1;
Lelemento `e nella lista. In questo caso il ciclo viene interrotto dallistruzione break
cosicch`e curr entry non `e uguale a NULL e la funzione ritorna il valore 0.
Esercizi
1. In generale `e utile che le funzioni che operano su un tipo di liste abbiano prototipi simili.
Modificare la funzione add sgl list() in modo che prenda gli stessi parametri della
funzione add ord sgl list().
2. Scrivere una funzione che cancella un elemento della lista sgle link t.
3. Scrivere una funzione che copia una lista su unaltra.
4. Scrivere una funzione che svuota e cancella una lista.
5. Riunire tutte le funzioni che operano sulla lista sgle link t in un modulo creando
anche il relativo file di header.
4.2. Stack
(Rev. 2.0.4)
Lo stack `e un esempio di struttura dati LIFO (Last In First Out) in cui i dati vengono aggiunti
e/o presi sempre dallinizio della lista. Uno stack pu`o essere visualizzato come una pila di
fogli uno sullaltro. Le operazioni principali su uno stack sono chiamate push e pop:
push aggiunge un elemento in cima alla lista:
push
new data
data 3
new data
data 2
data 3
data 1
data 2
data 1
Stack
Stack
505
yaC-Primer: Stack
(Rev. 2.0.4)
data 4
pop
data 4
data 3
data 3
data 2
data 2
data 1
data 1
Stack
Stack
Il seguente modulo, composto dal file stack.c dal file di header stack.h, realizza mediante
una single linked list un semplice stack che contiene dati di tipo char.
Header: stack.h
/* ***************************************************************
- Definizioni per lo Stack stack.c
- Funzioni :
stack_t
* init_stack (void );
void
push( stack_data data , stack_t *stck );
stack_data pop( stack_t *stck );
stack_data top( stack_t *stck );
int
filled ( const stack_t *stck );
- Descrizione :
init_stack ()
push ()
pop ()
top ()
filled ()
->
->
->
->
->
stack_data
506
yaC-Primer: Stack
/*
* Nodo dello Stack
*/
typedef struct entry {
struct entry next ;
stack_data
data ;
} stack_entry ;
/*
* Stack
*/
typedef struct stack {
stack_entry top ;
int count ;
} stack_t ;
(Rev. 2.0.4)
/* Elemento successivo */
/* Dato contenuto
*/
/*
* Funzioni pubbliche per lo Stack
*/
extern stack_t
init_stack ( void ) ;
extern void
push ( stack_data data , stack_t stck ) ;
extern stack_data pop ( stack_t stck ) ;
extern stack_data top ( stack_t stck ) ;
extern int
filled ( const stack_t stck ) ;
Note su stack.h
extern stack_t
init_stack ( void ) ;
....
Per poter utilizzare una funzione la cui definizione `e in un file diverso da quello in cui
viene utilizzata questa deve essere in classe di memorizzazione extern. La classe extern
non richiede che la dichiarazione sia necessariamente in un file diverso, di conseguenza il
file di header stack.h pu`o essere usato sia per il modulo, dove le funzioni sono definite,
che per i files che lo utilizzano.
Ricordiamo che se nella dichiarazione e/o definizione di una funzione non compare
nessuno specificatore di classe il compilatore assume la classe extern per cui la specificazione della classe di memorizzazione extern spesso viene omessa nella scrittura dei
moduli.
typedef char stack_data ;
Lutilizzo dellidentificatore di tipo stack data per i dati contenuti nello stack permette
di modificarne facilmente il tipo. Nellesempio lo stack contiene dati di tipo char.
typedef struct entry {
struct entry next ;
stack_data
data ;
/* Elemento successivo */
/* Dato contenuto
*/
507
yaC-Primer: Stack
(Rev. 2.0.4)
} stack_entry ;
typedef struct stack {
stack_entry top ;
int count ;
} stack_t ;
Si definiscono due tipi struttura. Il primo tipo stack entry `e il nodo o elemento
dello stack e contiene il valore del dato immesso nello stack e lindirizzo dellelemento
successivo nello stack. Gli elementi di tipo stack entry organizzati in una single linked
list costituiscono lo stack. Il secondo tipo stack t, introdotto per comodit`a, contiene
lindirizzo del primo elemento dello stack, ovvero lelemento in cima (top) allo stack,
ed il numero di elementi contenuti nello stack eliminando cos` la necessit`a di dover
contare gli elementi ogni volta che serva conoscerne il loro numero.
stack_t
top
stack_entry
data 4
count
data 3
data 2
data 1
Il file di header stack.h contiene tutte le definizioni e dichiarazioni necessarie per poter utilizzare il modulo. Queste costituiscono le informazioni pubbliche del modulo. Non contiene
invece nessuna definizione locale al modulo, che costituiscono le informazioni private del
modulo, n`e le definizioni delle funzioni. Tutte queste si trovano nel file stack.c che costituisce
il modulo vero e proprio.
Modulo: stack.c
/* ***************************************************************
- Descrizione : Stack
- $Id: stack.c v 2.0 21.10.04 AC
**************************************************************** */
# include " stack.h"
/* --* init_stack ()
*
* Inizializza lo stack
*/
extern stack_t init_stack ( void )
508
yaC-Primer: Stack
(Rev. 2.0.4)
{
stack_t s ;
s = ( stack_t ) malloc ( sizeof ( stack_t ) ) ;
if ( s == NULL ) return s ;
s>count = 0 ;
s>top
= NULL ;
return s ;
}
/* --* push ()
*
* Aggiunge un elemento in cima allo stack
*/
extern void push ( stack_data data , stack_t stck )
{
stack_entry entry ;
/* Crea il nuovo elemento dello stack */
entry = ( stack_entry ) malloc ( sizeof ( stack_entry ) ) ;
if ( entry == NULL ) {
fprintf ( stderr , "\n Failed to allocate space for the stack\n" ) ;
exit ( 1 ) ;
}
/* Mette il nuovo elemento in cima */
entry>data = data ;
entry>next = stck>top ;
stck>top
= entry ;
++stck>count ;
return ;
}
/* --* pop ()
*
* Ritorna lelemento in cima allo stack eliminandolo
*/
extern stack_data pop ( stack_t stck )
{
stack_data
d;
stack_entry e ;
/* prende lelemento in cima allo stack */
d = stck>top>data ;
e = stck>top ;
/* elimina lelemento dallo stack */
stck>top = stck>top>next ;
509
yaC-Primer: Stack
(Rev. 2.0.4)
stck>count ;
free ( e ) ;
return d ;
}
/* --* top ()
*
* Ritorna lelemento in cima allo stack senza eliminarlo
*/
extern stack_data top ( stack_t stck )
{
return stck>top>data ;
}
/* --* filled ()
*
* Ritorna 0 se lo stack e vuoto o il numero di elementi
*/
extern int filled ( const stack_t stck )
{
return stck>count ;
}
Questo modulo contiene le funzioni top() e filled() che ritornano semplicemente il valore
di campi specifici della struttura. La loro definizione pu`o quindi apparire superflua in quanto,
ad esempio, al posto delle funzione filled() si potrebbe sostituire direttamente il valore del
campo count della struttura di tipo stack t. Questo per`o richiede la conoscenza esplicita
dei campi della struttura. Luso della funzione filled() invece prescinde dalla conoscenza
esplicita dei campi della struttura rendendo cos` un eventuale programma che utilizzi il modulo
meno dipendente dai dettagli della realizzazione dello stack e quindi pi`
u flessibile.
/* ***************************************************************
- Descrizione : Scrive una stringa al contrario
(test per stack.c)
- $Id: test - stack.c v 2.0 21.10.04 AC
**************************************************************** */
# include <s t d i o . h>
# include <s t r i n g . h>
510
(Rev. 2.0.2)
/*
* Usa modulo stack.c
*/
# include " stack.h"
int main ( void )
{
char
str [ 8 1 ] ;
int
i;
stack_t stack ;
/* legge una stringa */
printf ( " Stringa
: ");
fgets ( str , sizeof ( str ) , stdin ) ;
str [ strlen ( str ) 1 ] = \0 ;
/* elimina return */
/* Inizializza lo stack */
stack = init_stack ( ) ;
/* aggiunge i dati allo stack */
for ( i = 0 ; str [ i ] != \0 ; ++i ) {
push ( str [ i ] , stack ) ;
}
/* scrive il contnuto dello stack */
printf ( " Stringa invertita : " ) ;
while ( filled ( stack ) ) {
printf ( "%c" , pop ( stack ) ) ;
}
printf ( "\n" ) ;
return 0 ;
}
o come
$ cc c teststack . c
$ cc c stack . c
$ cc teststack . o stack . o
511
(Rev. 2.0.2)
(Rev. 2.0.2)
Nelle liste Double Linked Lists (Liste con due connessioni) ogni elemento della lista contiene
informazioni sia sullelemento che lo segue che su quello che lo precede. La sua struttura `e
quindi della forma:
NULL
node_1
node_2
node_3
next
next
next
prev
prev
prev
data
data
data
NULL
Una double linked list differisce quindi da una single linked list solo per il fatto di avere due
links: uno in avanti ed uno indietro. Di conseguenza la definizione del nodo di una double
linked list `e della forma:
typedef struct double_link {
struct double_link next ;
struct double_link prev ;
data_type
data ;
} dble_link_t ;
Una volta definito il nodo della lista questa viene creata definendo un puntatore al primo
elemento della lista. Allinizio la lista `e vuota per cui il puntatore viene inizializzato con il
valore nullo NULL:
struct double_link first = NULL
ovvero
dble_link_t first = NULL ;
La presenza di due links rende le operazioni sulle double linked list in genere pi`
u complesse
di quelle sulle single linked lists.
void add_ord_dbl_list ( dble_link_t first , double data )
{
dble_link_t
new ;
/* nuovo elemento
*/
512
dble_link_t insert ;
(Rev. 2.0.2)
/* punto di inserimento */
La funzione prende due parametri: double data che contiene il valore del nuovo dato da
inserire, ed il puntatore al puntatore alla lista dble link t **first poich`e nel caso in cui il
nuovo elemento venga aggiunto allinizio della lista deve poter modificare il valore di *first
che contiene lindirizzo del primo elemento della lista.
513
(Rev. 2.0.2)
Dopo aver creato un nuovo elemento della lista ed avergli assegnato il valore la funzione ne
determina il punto di inserimento nella lista in base al suo valore.
Se la lista `e vuota oppure il nuovo elemento `e pi`
u piccolo di quelli gi`a presenti lelemento
viene inserito al primo posto:
if ( ( first == NULL ) | | ( data <= ( first)>data ) ) {
new>next = first ;
new>prev = NULL ;
if ( first != NULL ) ( first)>prev = new ;
first
= new ;
return ;
}
Ricordiamo che bisogna prima controllare se la lista `e vuota e solo nel caso che questa
non lo sia effettuare il controllo con il primo elemento della lista. Di conseguenza lordine
con cui sono scritte le due espressioni logiche nel test logico `e rilevante.
Linserimento viene effettuato in tre passi:
1. Creazione links del nuovo elemento
new>next = first ;
new>prev = NULL ;
Il campo next del nuovo elemento punta al primo elemento della lista, mentre il
campo prev viene inizializzato a NULL.
NULL
new
*first
next
next
prev
prev
data
data
NULL
new ;
Il campo prev del primo elemento della lista punta al nuovo elemento.
NULL
514
new
*first
next
next
prev
prev
data
data
(Rev. 2.0.2)
new ;
NULL
next
next
prev
prev
data
data
Se la lista non `e vuota ed il valore da inserire non `e minore del valore pi`
u piccolo
contenuto nella lista si determina la posizione del nuovo elemento nella lista scorrendola
con laiuto del puntatore ausiliario insert:
insert = first ;
while ( insert>next != NULL ) {
if ( data <= insert>next>data )
break ;
insert = insert>next ;
}
Nelle double linked list ogni nodo contiene le informazioni sia sul nodo successivo che
sul nodo precedente per cui un solo puntatore ausiliario `e sufficiente per aprire e
richiudere la lista senza spezzarla. Il puntatore insert identifica lelemento dopo il
quale si deve inserire il nuovo elemento. Di conseguenza per inserire i dati ordinati
in valore crescente basta scorrere la lista fino a quando il valore del dato contenuto
nellelemento successivo a insert `e minore del valore da inserire, ovvero si raggiunge la
fine della lista. Osserviamo che per accedere al valore del dato contenuto nellelemento
succesivo a insert si usa insert->next->data perch`e sia insert che il campo next
sono puntatori ad una struttura.
Se la condizione `e verificata il ciclo while viene interrotto dallistruzione break altrimenti, se il valore da inserire `e pi`
u grande di tutti quelli gi`a contenuti nella lista, il ciclo
si interrompe quando viene raggiunta la fine della lista. In questultima eventualit`a il
puntatore insert indica lultimo elemento della lista cosicch`e il nuovo elemento viene
aggiunto in coda alla lista.
Determinata la posizione di inserzione il nuovo elemento viene aggiunto alla lista:
1. Creazione links del nuovo elemento
new>next = insert>next ;
new>prev = insert ;
Il campo next del nuovo elemento new punta allelemento successivo a insert,
mentre il campo prev punta a insert.
515
(Rev. 2.0.2)
insert
insert>next
next
next
prev
prev
data
new
data
next
prev
data
new>next
next
next
prev
prev
data
new
data
next
prev
data
Se insert non `e lultimo elemento della lista, il campo prev dellelemento successivo a new deve puntare al nuovo elemento new.
516
new>next
next
next
prev
prev
data
new
(Rev. 2.0.2)
data
next
prev
data
int cnt_dbl_list ( dble_link_t first )
{
int cnt ;
/* Se la lista e vuota ritorna 0 */
if ( first == NULL ) return 0 ;
/* altrimenti conta gli elementi */
cnt = 1 ;
while ( first>next != NULL ) {
++cnt ;
first = first>next ;
}
return cnt ;
}
Se il numero di elementi della lista `e grande il conteggio pu`o risultare ineffienciente. In questi
casi conviene utilizzare una strategia analoga a quella utilizzata per lo stack definendo un
tipo struttura aggiuntivo che contenga lindirizzo del primo elemento della lista ed il numero
di elementi della lista e modificando di conseguenza le funzioni che operano sulla lista.
517
(Rev. 2.0.2)
518
(Rev. 2.0.3)
17.839530
39.967747
16.659879
21.212187
6.193571
5.414984
27.566549
4.775724
32.103319
27.273368
Esercizi
1. Scrivere una funzione che effettua la ricerca di un elemento nella lista dble link t.
2. Scrivere una funzione che cancella un elemento della lista dble link t;
3. Scrivere una funzione che copia una lista su unaltra.
4. Scrivere una funzione che svuota e cancella una lista.
5. Riunire tutte le funzioni che operano sulla lista dble link t in un modulo creando
anche il relativo file di header.
(Rev. 2.0.3)
La ricerca di un elemento in una lista lineare `e un processo piuttosto lento in quanto richiede il
controllo di ogni elemento della lista fino a quando non si trova quello desiderato. Questo procedimento pu`o diventare straordinariamente lento per liste molto lunghe. I tempi di ricerca
519
(Rev. 2.0.3)
possono essere ridotti notevolmente utilizzando strutture di dati chiamati alberi (trees). A
causa della loro efficienza le strutture di dati ad albero trovano largo uso in problemi che
richiedano limmagazzinamento di grandi quantit`a di dati, ed in particolare in molti processi
di sistema come ad esempio nella compilazione di un programma per immagazzinare le variabili usate dal programma o la gestione di un computer multi-utente per immagazzinare le
usernames del computer.
La forma pi`
u semplice di albero `e quello chiamato Albero Binario (Binary Tree) in cui ogni
nodo `e connesso ad altri due nodi:
Top
data
l
Level 1
data
data
NULL
Level 2
NULL
data
data
data
r
NULL
NULL
r
NULL
NULL
r
NULL
/* nodo a sinistra */
/* nodo a destra
*/
/* dato del nodo
*/
};
In questo esempio i dati contenuti nellalbero sono di tipo int tuttavia, come per le altre
liste, `e possibile definire alberi che contengano dati di un tipo qualsiasi purch`e ammesso come
campo di una struttura.
Il nodo principale (Top) da cui parte lalbero `e chiamato cima (top), o a volte padre, mentre gli
altri sono chiamati nodi di primo livello o generazione (Level 1), secondo livello o generazione
(Level 2) e cos` via allaumentare della distanza dal noto in cima.
Come esempio di utilizzo di un albero binario considereremo il problema di ordinare in ordine
crescente una serie di dati numerici. Il metodo naive di ordinare i dati confrontando ogni
nuovo dato con quelli gi`a ordinati richiede un numero di operazioni dellordine del numero
di dati da ordinare. Esistono, tuttavia, algoritmi pi`
u efficienti, come ad esempio il mergesort
basato sullunione (merge) di sottosequenze ordinate (sort) di dati, che richiedono un numero
di operazioni che cresce come il logaritmo del numero di dati. Lordinamento basato su un
albero binario appartiene a questa categoria.
520
(Rev. 2.0.3)
Costruzione dellalbero
Lalbero viene costruito in modo tale che preso un nodo qualsiasi dellalbero il (sotto)albero
a sinistra del nodo contenga tutti dati di valore minore o uguale a quello contenuto nel nodo
scelto, mentre il (sotto)albero a destra contenga dati di valore maggiore. Questo `e ottenuto
mediante il seguente semplice algoritmo.
Partendo dal nodo in cima allalbero (Top)
1. Se il nodo non `e vuoto il nuovo dato si inserisce nel (sotto)albero a sinistra o a destra
a seconda che il valore del dato da inserire sia pi`
u piccolo o pi`
u grande del valore del
dato contenuto del nodo.
2. Si ripete loperazione fino a quando non si raggiunge un nodo vuoto (NULL) alla fine di
un ramo dellalbero.
3. Se il nodo `e vuoto si crea un nuovo nodo contenente il nuovo dato e lo si aggiunge alla
fine del ramo raggiunta;
Con questo algoritmo se lalbero `e vuoto il primo dato viene inserito nel nodo in cima (Top)
allalbero. I dati successivi sono aggiunti discendendo lalbero lungo i rami passando da un
nodo ad un nodo del livello successivo fino a raggiungere la fine di un ramo dove viene aggiunto
un nuovo nodo contenente il nuovo dato.
Come esempio consideriamo linserimento del numero 13 nellalbero riportato in figura:
Top
17
l
Level 1
10
l
25
r
NULL
Level 2
5
l
NULL
15
r
l
NULL
NULL
95
r
l
NULL
NULL
NULL
Level 3
521
(Rev. 2.0.3)
17
13 < 17
Level 1
10
l
25
r
13 > 10
NULL
Level 2
5
l
NULL
15
13 < 15
r
NULL
Level 3
95
r
l
NULL
NULL
r
NULL
13
l
NULL
NULL
Osserviamo che per inserire un nuovo nodo si esegue la stessa operazione su tutti i nodi su
cui si passa discendendo lalbero. Inoltre il procedimento ha chiaramente una fine: quando
si raggiunge un nodo vuoto, ossia la fine di un ramo dellalbero. Lalgoritmo si presta quindi
molto bene ad una scrittura ricorsiva della funzione di inserimento.
La possibilt`a del linguaggio C di poter scrivere funzioni ricorsive, ossia che richiamano se
stesse, risulta molto utile nella scrittura di funzioni che operano su alberi perch`e permette
di scrivere funzioni molto chiare ed allo stesso tempo molto compatte. Tutte le funzioni del
modulo b tree.c riportato qui di seguito sono scritte in forma ricorsiva.
Modulo
Header: b tree.h
/* ***************************************************************
- Definizioni per lAlbero Binario
b_tree .c
- Funzioni :
void b_tree_enter ( node_t **node , b_tree_data data );
void b_tree_print ( node_t *top );
void b_tree_size ( node_t *top , int *n)
- Descrizione :
b_tree_enter () -> Aggiunge un elemento all albero
b_tree_print () -> Stampa il contenuto dell albero
b_tree_size () -> # nodi albero
522
b_tree_data
(Rev. 2.0.3)
/* nodo a sinistra */
/* nodo a destra
*/
/* dato del nodo
*/
/* ***************************************************************
- Descrizione : Albero Binario
- $Id: b_tree .c v 1.2 23.10.04 AC
**************************************************************** */
# include <s t d i o . h>
# include " b_tree .h"
/*
* Funzioni Private
*/
static void size_b_tree ( node_t top , int cnt ) ;
/* --* b_tree_enter ()
*
* Aggiunge un nodo all albero
*/
extern void b_tree_enter ( node_t node , b_tree_data data )
{
/*
* Se il nodo corrente e NULL si e raggiunto
* fondo dell albero ->
nuovo nodo
*/
if ( node == NULL ) {
523
(Rev. 2.0.3)
524
(Rev. 2.0.3)
*
* Conta i nodi dell albero
*/
static void size_b_tree ( node_t top , int cnt )
{
if ( top != NULL ) {
size_b_tree ( top>left , cnt ) ;
++(cnt ) ;
size_b_tree ( top>right , cnt ) ;
}
return ;
}
Note su b tree.c
Funzione b tree enter()
extern void b_tree_enter ( node_t node , b_tree_data data )
Per poter aggiungere un nuovo nodo si deve modificare il campo left o right del nodo
alla fine del ramo e quindi di un nodo del livello precedente a quello del nuovo nodo. Di
conseguenza siccome i campi left e right sono puntatori a node t la funzione prende
come parametro un puntatore a puntatore a node t cosicch`e quando la funzione viene
chiamata node contiene lindirizzo di memoria del campo left o right del nodo del
livello precedente che ha chiamato la funzione che pu`o quindi essere modificato.
if ( node == NULL ) {
/* Creazione nuovo nodo */
node = ( node_t ) malloc ( sizeof ( node_t ) ) ;
if ( node == NULL ) {
fprintf ( stderr , "\ nFailed to get space for the tree\n" ) ;
exit ( 1 ) ;
}
/* Inizializzazione nuovo nodo */
( node)>data = data ;
( node)>left = NULL ;
/* Ultimo nodo del ramo */
( node)>right = NULL ;
return ;
}
Se il valore di *node, ossia del campo left o right del nodo che ha chiamato la funzione,
`e NULL si `e raggiunta la fine di un ramo dellalbero. Si deve quindi creare un nuovo nodo
ed aggiungerlo alla fine del ramo:
node = ( node_t ) malloc ( sizeof ( node_t ) ) ;
525
(Rev. 2.0.3)
Essendo node il puntatore al campo left o right del nodo di livello superiore che ha
chiamato la funzione questa istruzione ne modifica il contenuto cosicch`e questo adesso
punta al nuovo nodo il quale viene cos` collegato a quello di livello superiore. Il nuovo
nodo `e collegato alla sinistra o alla destra del nodo di livello superiore a seconda che
node punti al campo left o right.
Dopo aver creato ed aggiunto il nodo allalbero vi si inserisce il nuovo dato
( node)>data
= data ;
e si annullano i suoi campi left e right in quanto il nodo si trova alla fine di un
ramo:
( node)>left = NULL ;
( node)>right = NULL ;
Se il nodo non `e vuoto il nuovo dato va inserito nel (sotto)albero a sinistra o a destra
a seconda che il suo valore sia pi`
u piccolo (o uguale) o pi`
u grande del valore del dato
contenuto nel nodo.
Il controllo viene fatto con loperatore condizionale ?: la cui sintassi `e:
expr1 ? expr2 : expr3
passa lindirizzo del puntatore al (sotto)albero, in questo caso il destro. Infatti questa
viene valutata come:
*node := Valore delloggetto puntato da node, ossia la struttura.
(*node)->right := Loggetto puntato da node `e una struttura di tipo node t di
cui se ne prende il campo right;
&(*node)->right := Indirizzo di memoria del campo right della struttura puntata da node.
526
(Rev. 2.0.3)
Le parentesi sono necessarie poich`e loperatore di selezione -> ha un livello di precedenza pi`
u alto delloperatore di dereferenza * per cui lespressione *node->right
verrebbe valutata come *(node->right) ossia il contenuto del campo right della
struttura puntata da node che non `e il risultato voluto in quanto node punta ad un
puntatore ad una struttura e non alla struttura stessa.
Funzione b tree print()
Per costruzione ad ogni nodo dellalbero i dati contenuti nel nodo e nei due nodi discendenti al livello inferiore sono ordinati, di conseguenza nonostante la struttura dellalbero
piuttosto complessa stamparne il contenuto in forma ordinata `e relativamente semplice.
Infatti basta stampare in forma ordinata partendo dal nodo Top in cima allalbero il
valore del dato contenuto nel nodo e nei due nodi discendenti ripetendo loperazione per
tutti i nodi mano a mano che ci si muove sullalbero.
Lalgoritmo di stampa `e quindi:
1. Se il nodo `e vuoto (NULL) non si fa nulla;
2. Se il nodo non `e vuoto si stampa il valore del dato che si trova nel nodo al livello
inferiore nel (sotto)albero a sinistra (valore minore), poi quello del dato che si
trova nel nodo ed infine quello del dato che si trova nel nodo al livello inferiore nel
(sotto)albero a destra (valore maggiore).
` facile convincersi che se si parte dal nodo in cima allalbero questo algoritmo stampa
E
in forma ordinata tutto il contenuto dellalbero. Anche questo algoritmo si scrive molto
facilmente utilizzando funzioni ricorsive:
extern void b_tree_print ( node_t top )
{
if ( top != NULL ) {
b_tree_print ( top>left ) ;
printf ( "data: %f\n" , top>data ) ;
b_tree_print ( top>right ) ;
}
return ;
}
La funzione size b tree() utilizza un algoritmo molto simile a quello utilizzato per
stampare del contenuto dellalbero, la sola differenza `e nel fatto che invece di stampare
il contenuto del nodo aumenta di uno il valore del contatore dei nodi cnt.
527
(Rev. 2.0.3)
Test dellalbero
Il seguente programma illustra luso dellalbero binario definito nel modulo b tree.c per
ordinare un array.
Programma: test-b tree.c
/* ***************************************************************
- Descrizione : Ordina una array di numeri
(test per b_tree .c)
- $Id: test - b_tree .c v 1.2 23.10.04 AC
**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
/*
* Usa modulo b_tree .c
*/
# include " b_tree .h"
void init_darray ( double a , int n ) ;
void print_darray ( double a , int n ) ;
int main ( void )
{
int
i, n;
double
array ;
node_t tree = NULL ;
printf ( "\n%s" , " Dimensione array [< 1 exit ]: " ) ;
scanf ( "%d" ,&n ) ;
if ( n < 1 ) {
printf ( "\nYou are joking !\n" ) ;
return 1 ;
}
array = ( double ) malloc ( n sizeof ( double ) ) ;
if ( array == NULL ) {
printf ( "\n Array too long !!!!\n\n" ) ;
exit ( 1 ) ;
}
init_darray ( array , n ) ;
printf ( "\n\ tArray \n\n" ) ;
print_darray ( array , n ) ;
for ( i = 0 ; i < n ; ++i ) {
b_tree_enter(&tree , array [ i ] ) ;
}
528
(Rev. 2.0.3)
ovvero come
$ cc c testb_tree . c
$ cc c b_tree . c
$ cc testb_tree . o b_tree . o
529
array [ 0 ]
array [ 1 ]
array [ 2 ]
array [ 3 ]
array [ 4 ]
array [ 5 ]
array [ 6 ]
array [ 7 ]
array [ 8 ]
array [ 9 ]
=
=
=
=
=
=
=
=
=
=
(Rev. 2.0.3)
0.742788
0.631704
0.118309
0.922271
0.141282
0.808310
0.961468
0.363704
0.665483
0.683465
Tree Size : 10
Tree
data :
data :
data :
data :
data :
data :
data :
data :
data :
data :
0.118309
0.141282
0.363704
0.631704
0.665483
0.683465
0.742788
0.808310
0.922271
0.961468
Esercizi
1. Modificare il modulo b tree.c in modo da includere altri tipi di dati.
2. Riscrivere le funzioni del modulo usando una formulazione iterativa.
3. Scrivere una funzione che copia un albero su un altro ed una che lo copia su un array.
530
5. Numeri Random
5.1. Generatori di Numeri Random
(Rev. 2.0.1)
Spesso quando si deve simulare un dato processo fisico si ha la necessit`a di dover disporre
di sequenze di numeri random (aleatori) con date caratteristiche. Lutilizzo di sequenze di
numeri random non `e tuttavia ristretto al campo della fisica, ad esempio nel campo delle
comunicazioni i numeri random sono alla base delle procedure per criptare le informazioni.
I programmi che producono sequenze di numeri random sono chiamati generatori di numeri
random.
A ben pensarci pu`o sembrare quantomeno strano voler utilizzare un computer per generare
numeri random. Il risultato di un qualsiasi programma `e infatti il risultato di una sequenza
ben precisa di istruzioni e quindi completamente deterministico. Di conseguenza anche la
sequenza di numeri generati da un generatore di numeri random deve essere deterministica.
Nonostante quello che possa sembrare questo tuttavia non elimina la possibilit`a di realizzare
generatori di numeri random.
Tutti i generatori di numeri random si basano sulle seguenti osservazioni. Per prima cosa
`e chiaro che in nessun caso un generatore di numeri random pu`o produrre una sequenza
di numeri random di lunghezza arbitraria. Infatti, siccome i numeri rappresentabili su un
computer sono in numero finito, una qualsiasi regola deterministica che dato un numero dia
il successivo nella sequenza, prima o poi necessariamente produrr`a un numero gi`a ottenuto in
precedenza. Da questo numero in poi, siccome la regola `e deterministica, la sequenza di numeri
generati sar`a la stessa di quella gi`a ottenuta in precedenza a partire da questo numero. La
sequenza `e quindi periodica. La lunghezza della sequenza senza ripetizioni definisce il periodo
del generatore.
Siccome per accorgersi della periodicit`a del generatore bisogna generare sequenze pi`
u lunghe
del periodo, ogni sequenza di numeri di lunghezza inferiore al periodo, anche se deterministica,
` chiaro che il periodo di un generatore di numeri
pu`o essere considerata non periodica. E
random deve essere il pi`
u grande possibile. In generale il periodo pu`o dipendere, oltre che dai
parametri del generatore, anche dal numero da cui parte la sequenza.
La propriet`a dei generatori di numeri random `e quella di produrre sequenze deterministiche di
numeri r1 , r2 , . . . , rN tali che ogni sequenza non periodica abbia le stesse propriet`a statistiche
di una sequenza di numeri random x1 , x2 , . . . , xN indipendenti ed identicamente distribuiti1
con una distribuzione probabilit`a p(x) voluta. Per sottolineare questo aspetto spesso le sequenze di numeri generati da un computer sono chiamate pseudo-random.
1
531
(Rev. 2.0.1)
Esiste tutta una serie di test statistici per controllare la bont`a di un generatore di numeri
random. Questi vanno dal semplice controllo che i primi momenti della distribuzione di probabilit`a generata coincidano nel limite di una sequenza molto lunga con quelli della distribuzione
teorica richiesta allanalisi delle correlazioni che vi possono essere nella sequenza.
Non ci addentreremo nei vari aspetti di questa questione anche perch`e spesso non `e necessario
conoscere esattamente tutte le propriet`a statistiche della sequenza di numeri pseudo-random.
Infatti per molte applicazioni quello che conta sono i risultati del programma che utilizza un
generatore di numeri random, piuttosto che le propriet`a della sequenza di numeri pseudorandom utilizzata. Di conseguenza in questi casi si pu`o adottare la seguente procedura:
Se in un programma che usa numeri pseudo-random si cambia il generatore di
numeri random i risultati devono rimanere statisticamente invariati.
Chiaramente in questo modo la bont`a di un generatore di numeri random viene a dipendere
dallutilizzazione fatta: quello che `e random abbastanza per unapplicazione potrebbe non
esserlo per unaltra. Tuttavia sebbene non completamente preciso, questo punto di vista `e
sufficiente per molti scopi. Inoltre siccome di regola il tempo necessario per generare un
numero random cresce con bont`a del generatore, un approccio in cui la bont`a si misura
sui risultati prodotti permette di scrivere programmi pi`
u efficienti.
Questo, tuttavia, non deve far dimenticare lesistenza di tests per giudicare la bont`a di
un generatore di numeri random indipendentemente dal suo utilizzo. Un buon generatore di
numeri random deve passare tutti i tests. Alternativamente `e necessario sapere quali tests
fallisce per poterne giudicare lopportunit`a di un suo utilizzo.
(Rev. 2.0.1)
Tra tutti i generatori di numeri random quelli di numeri random uniformi in un intervallo
rivestono un ruolo di primo piano in quanto praticamente tutti i generatori di numeri random
con distribuzioni di probabilit`a non uniformi sfruttano uno o pi`
u generatori di numeri random
con distribuzione uniforme in qualche intervallo.
Numeri random con distribuzione uniforme nellintervallo [a, b] sono numeri random iid che
possono assumere con la stessa probabilit`a qualsiasi valore nellintervallo [a, b], di conseguenza
la loro distribuzione di probabilit`a p(x) `e:
ba axb
p(x) =
0
altrimenti
In particolare i numeri random con distribuzione uniforme nellintervallo chiuso [0, 1] sono
numeri che possono prendere con la stessa probabilit`a un valore qualsiasi tra 0 e 1, per i quali
si ha quindi
1 0x1
p(x) =
0 altrimenti
532
(Rev. 2.0.1)
Gli estremi dellintervallo possono essere inclusi od esclusi a seconda che la distribuzione sia
in un intervallo chiuso [a, b], aperto (a, b) o semi-aperto (a, b] o [a, b):
[a, b]
axb
(a, b)
a<x<b
[a, b)
ax<b
(a, b]
a<xb
Il modo pi`
u semplice di generare una sequenza di numeri pseudo-random con distribuzione
uniforme `e con un generatore basato sulle congruenze lineari. Un generatore che utilizza le
congruenze lineari produce una sequenza di numeri interi I1 , I2 , I3 ,... con valore tra 0 e m 1
inclusi usando la formula di ricorrenza
In+1 = a In + c
(mod m)
dove
m
a
c
:=
:=
:=
modulo
moltiplicatore
incremento
/* ***************************************************************
533
(Rev. 2.0.1)
Modulo: randu.c
/* ***************************************************************
La sequenza viene inizializzata dalla funzione srandu() che inizializza il generatore fissando
534
(Rev. 2.0.1)
il primo numero della sequenza IO uguale a al valore del seme seed. Ogni valore di seed
produce una sequenza differente di numeri pseudo-random o un punto di partenza diverso di
una sequenza ciclica. Tuttavia lo stesso valore di seed produrr`a sempre la stessa sequenza di
numeri pseudo-random. Se il generatore non viene inizializzato con la funzione srandu() il
generatore produce la sequenza di numeri che avrebbe generato dopo essere stato inizializzato
con un seed di valore 1.
Per generare la sequenza di numeri pseudo-random, dopo aver inizializzato il generatore, si
chiama ripetutamente la funzione randu() come mostrato nel seguente programma di prova
del modulo. Ogni chiamata della funzione ritorna il numero successivo nella sequenza generata
a partire da I0 . Il modulo utilizza la variabile privata next per memorizzare lultimo numero
generato. I numeri generati da randu() sono interi con valori compresi tra 0 e 32767 inclusi.
Se si desiderano dei numeri uniformi nellintervallo [0, 1) si pu`o usare la funzione drandu().
Esempio: Test di randu.c
/* ***************************************************************
- Descrizione : test per randu.c
- $Id: test - randu.c v 1.0 24.10.04 AC
**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
/*
* Usa modulo randu.c
*/
# include "randu .h"
int main ( void )
{
int i , n ;
srandu ( 1 2 3 4 5 ) ;
n = 10000;
for ( i = 0 ; i < n ; ++i ) {
printf ( "%d %f\n" , randu ( ) , drandu ( ) ) ;
}
return 0 ;
}
Sebbene generatori di numeri random basati sulle congruenze lineari possano produrre delle
sequenze di numeri raggionevolmente random, spesso la scelta dei parametri produce generatori di numeri random piuttosto questionabili. Un problema tipico `e dovuto al fatto che
spesso il valore di m non `e molto grande. Lo standard C richiede che m sia almeno uguale a
215 = 32768, scelta che pu`o rivelarsi un disastro se servono sequenze molto lunghe come ad
esempio nellintegrazione con il metodo Monte Carlo dove spesso si usano sequenze di 106 o
pi`
u numeri random.
Un altro svantaggio degli generatori di numeri random basati sulle congruenze lineari sono
535
(Rev. 2.0.1)
le correlazioni che vi sono tra i numeri della sequenza I1 , I2 , I3 , ... generati dalle chiamate
successive del generatore. Se le sequenze di k numeri successivi Ii , Ii+1 , ..., Ii+k1 sono usate
per fare un grafico k-dimensionale i punti non tendono a riempire uniformemente lo spazio
ma piuttosto si dispongono su piani di dimensione k 1. Ci sono al massimo m1/k piani,
ma se i parametri non sono scelti bene questi possono essere molto di meno. Ad esempio se
m = 32678 = 215 in tre dimensioni si hanno al massimo 32 piani.
Forse lesempio pi`
u noto `e il generatore di numeri RANDU utilizzato per anni sui computer
IBM. Questo utilizza un algoritmo di congruenze lineari con a = 65539, m = 231 e c = 0.
Con questa scelta il numero di piani in tre dimensioni si riduce a 11.
Le correlazioni tra i numeri generati non sono lunico punto debole dei generatori di numeri
random basati sulle congruenze lineari. Spesso infatti questi generatori producono sequenze
di numeri i cui bits meno significativi sono molto meno random, ossia hanno un periodo pi`
u
corto, dei bits pi`
u significativi. Di conseguenza se ad esempio si vuole generare dei numeri
distribuiti uniformemente tra 1 e 10 si deve usare
j = 1 + ( int ) ( 1 0 . 0 randu ( ) / ( double ) RANDU_MOD ) ;
Successive chiamate della funzione rand() generano una sequenza di numeri interi pseudorandom di tipo int con distribuzione uniforme nellintervallo 0 e RAND MAX. Il valore della
macros RAND MAX definito nel file stdlib.h rappresenta il valore dellintero pi`
u grande che la
funzione rand() pu`o generare. Lo standard C richiede che RAND MAX sia almeno 32767, ma
su molti sistemi questo `e pi`
u grande.
La funzione srand() inizializza il generatore utilizzando il valore del seme seed. Il valore del
seme identifica in maniera univoca la sequenza di numeri pseudo-random generati dalle chiamate successive del generatore. Di conseguenza se la funzione srand() viene richiamata con
536
(Rev. 2.0.1)
lo stesso seme con cui il generatore `e stato inizializzato dopo che questo ha prodotto una sequenza di numeri pseudo-random da questo punto in poi il generatore riprodurr`a esattamente
la stessa sequenza di numeri pseudo-random.
Se la funzione srand() non viene chiamata prima di utilizzare il generatore rand() questo
generer`a la sequenza di numeri pseudo-random che avrebbe generato se fosse stato inizializzato
con la funzione srand() con il seed uguale 1.
Su molti sistemi il generatore di numeri random di sistema rand() non `e basato sulle congruenze lineari ma su algoritmi che forniscono sequenze di numeri pseudo-random com propriet`a statistiche migliori.
Esercizi
1. Utilizzare triplette di numeri ottenuti con il generatore randu() e graficare i risultati
sui piani xy, xz, e yz e confrontare il risultato con il grafico ottenuto utilizzando il
generatore di sistema rand().
2. Calcolare il valore medio e la varianza dei numeri generati dal generatore drandu()
al variare della lunghezza della sequenza usata. Verificare che allaumentare della
lunghezza della sequenza questi tendono valori teorici aspettati 1/2 e 1/12.
3. Fare un istogramma dei numeri generati dal generatore drandu() e confrontarlo con la
distribuzione di probabilit`a dei numeri random con distribuzione uniforme nellintervallo
[0, 1]. Cosa succede al variare della lunghezza della sequenza di numeri usata?
4. Ripetere gli esercizi precedenti cambiando i parametri del generatore di numeri random.
(Rev. 2.0.1)
Un modo molto semplice per generare numeri pseudo-random con distribuzione di probabilit`a
Gaussiana:
1
x2
p(x) =
exp 2
2
2 2
sfrutta il Teorema del Limite Centrale. Dal questo teorema sappiamo infatti che se xn sono
N numeri random iid con una distribuzione di probabilt`a a valor medio nullo, allora per N
grande la grandezza
N
1 X
y=
xi
N i=1
`e un numero random con distribuzione di probabilit`a Gaussiana a valor medio nullo e varianza
uguale a quella delle xn .
Il seguente programma sfrutta questa propriet`a con numeri xn distribuiti uniformemente
nellintervallo [1, 1] cosicch`e la varianza della Gaussiana `e uguale a 1. I numeri generati
vengono scritti sullo stdout.
Programma: gen gauss-1.c
537
(Rev. 2.0.1)
/* ***************************************************************
- Descrizione :
Genera SAMP numeri Gaussiani come
y = (x_1 + x_2 + ... + x_N) / sqrt(N)
dove x_n sono numeri distribuiti uniformemente
tra -1 e 1
- Input :
- Output :
538
(Rev. 2.0.1)
Il programma scrive sullo stdout per cui se serve salvare i numeri generati in un file si
deve modificare il programma o, alternativamente, utilizzare la possibilit`a della shells
UNIX di mandare lo stdout su un file con loperatore >. Per cui se volessimo salvare
i numeri generati nel file gauss-5.dat, basta eseguire il comando:
$ a . out > gauss 5. dat
Esercizi
1. Calcolare il valore medio e la varianza dei numeri Gaussiani generati al variare di N e
SAMP.
2. Fare un istogramma dei numeri Gaussiani generati e confrontarlo con la distribuzione
Gaussiana al variare di N e SAMP.
(Rev. 2.0.1)
dx
.
dy
539
(Rev. 2.0.1)
Questa relazione `e valida per ogni distribuzione di probabilit`a p(x). Se ora ci limitiamo al
caso in cui la variabile x abbia una distribuzione di probabilit`a uniforme nellintervallo [0, 1]:
x [0, 1]
p(x) = 1
dalla relazione precedente segue che la distribuzione di probabilit`a per la variabile y soddisfa
lequazione differenziale
dx
= p(y),
x [0, 1]
dy
la cui soluzione `e
Z
y(x)
dy p(y).
x = F (y(x)) =
Osserviamo che F (y) `e la probabilit`a cumulativa della variabile random y. Invertendo infine
questa relazione otteniamo la trasformazione cercata
y(x) = F 1 (x)
dove F 1 `e la funzione inversa di F , che ci permette di passare da una variabile random x
con distribuzione di probabilit`a uniforme nellintervallo [0, 1] alla nuova variabile random y(x)
con distribuzione di probabilit`a p(y).
La messa in pratica del metodo dellinversione richiede richiede sia la conoscenza della primitiva F (y) della funzione di distribuzione voluta p(y) sia la possibilit`a di invertirla. Qui di
seguito considereremo alcuni semplici casi in cui queste due operazioni sono possibili. Tuttavia
spesso una delle due operazioni, od entrambi, non sono disponibili o richiedono un numero
elevato di operazioni. In questi casi si devono utilizzare altri metodi per generare i numeri
random.
ba
p(y) =
ayb
altrimenti
ya
ba
540
(Rev. 2.0.1)
(1/a) exp(y/a) y 0
p(y) =
0
y<0
Anche in questo caso lintegrale `e immediato
x = F (y) = 1 exp(y/a)
per cui invertendo la funzione F (y(x)) si ottiene la trasformazione y = a ln(1 x). Osservando infine che 1 x ha la stessa distribuzione di x, si ottiene la trasformazione
y(x) = a ln(x)
tra la variabile random x con distribuzione di probabilit`a uniforme nellintervallo [0, 1] alla
variabile random y con distribuzione di probabilit`a esponenziale nellintervallo [0, ).
y2 = R sin .
2 /2
x2 =
541
(Rev. 2.0.2)
Esercizi
1. Scrivere i generatori di numeri random con distribuzione uniforme nellintervallo [a, b],
con distribuzione esponenziale e distribuzione Gaussiana. Raggruppare le tre funzioni
in un modulo.
2. Studiare landamento dei primi due momenti delle distribuzioni di probabilit`a in funzione della lunghezza N della sequenza utilizzata riportandone il valore su un grafico in
funzione di N a . Il parametro di a deve essere fissato in modo che i dati cadano su una
retta.
(Rev. 2.0.2)
Un metodo molto generale per generare numeri random con una distribuzione di probabilit`
a
qualsiasi `e il metodo Monte Carlo introdotto da Metropolis, Rosenbluth, Rosenbluth, Teller
e Teller nel 1953, e da allora noto come il metodo Monte Carlo Metropolis. Il metodo Monte
Carlo `e un metodo molto versatile e potente e per questo trova applicazione in molti campi
diversi. In fisica il metodo Monte Carlo `e alla base, ad esempio, di molti metodi di simulazione
utilizzati in meccanica statistica ed altri campi della fisica. Il metodo Monte Carlo pu`o essere,
infine, utilizzato anche per calcolare numericamente integrali in una o pi`
u dimensioni.
Il metodo si basa sulla generazione di numeri di prova che vengono accettati o riggettati in
modo da ottenere la distribuzione voluta. Per semplicit`a descriveremo il metodo nel caso di
una sola variabile random, discreta o continua, ma come risulter`a dalla descrizione che segue
il metodo pu`o essere facilmente esteso al caso di distribuzioni di probabilit`a p(x1 , x2 , ..., xN )
di un numero qualsiasi di variabili.
Supponiamo quindi di voler generare dei numeri random x distribuiti con una distribuzione
di probabilit`a p(x) fissata. Lidea di base del metodo Monte Carlo `e quella di generare una
sequenza di numeri x1 , x2 , . . . , xN la cui distribuzione di frequenza asintotica ottenuta nel
limite di sequenze molto lunghe, N 1, tenda alla distribuzione di probabilit`a voluta p(x).
La sequenza viene costruita specificando per ogni coppia di valori x e x0 che la variabile
random pu`o assumere la probabilit`a di transizione T (x x0 ) definita come:
T (x x0 ) := probabilit`a che dato x il prossimo valore sia x0 .
Dalla definizione `e chiaro che per ogni valore di x le probabilit`a di transizione T (x x0 )
devono soddisfare la condizione di normalizzazione:
X
T (x x0 ) = 1
x
x0
dove la somma `e estesa a tutti i possibili valori che la variabile random pu`o assumere. Questa
relazione esprime semplicemente il fatto che ad ogni valore x segue necessariamente nella
sequenza un altro valore qualsiasi tra quelli che la variabile random pu`o assumere. Nel caso
che la variabile possa assumere valori continui, la somma va sostituita con un integrale.
542
(Rev. 2.0.2)
Le sequenze generate con probabilit`a di transizione che dipendono solo dal valore di partenza
e di arrivo sono chiamate catene o processi di Markov. Questi processi hanno la propriet`a che
la probabilit`a p(x; N ) che la variabile random assuma il valore xN = x nella posizione N della
sequenza dipende solo dal valore xN 1 = x0 che la variabile ha nella posizione precedente e
non da tutti i valori gi`a presi dalla variabile, ossia da tutta la sua storia passata.
Per mostrare ci`o consideriamo le sequenze di lunghezza N , queste sono banalmente ottenute
dalle sequenze di lunghezza N 1 aggiungendo un nuovo elemento xN . Siccome le probabilit`a
di transizione dipendono solo dal valore di partenza e di arrivo il valore del nuovo elemento
xN dipender`a solo da quello di xN 1 e non da tutti i valori della sequenza x1 , x2 , . . . , xN 2 .
Di conseguenza la probabilit`a p(x; N ) di avere il valore xN = x in una sequenza di N numeri
`e data dalla le probabilit`a p(x0 ; N 1) di avere il valore xN 1 = x0 in una sequenza di N 1
numeri e che a questo segua il valore xN = x sommata su tutti i possibili valori di x0 di xN 1 .
In formule:
X
p(x; N ) =
p(x0 ; N 1) T (x0 x).
x0
Questo risultato si pu`o ottenere anche osservando siccome ciascun valore nella sequenza
dipende solo da quello precedente la probabilit`a p(x1 , x2 , . . . , xN ) di osservare la sequenza
di valori x1 , x2 , . . . , xN in una sequenza di lunghezza N che inizia con il valore x0 `e
p(x1 , x2 , . . . , xN ) = T (x0 x1 ) T (x1 x2 ) T (xN 1 xN )
ossia che al valore x0 segua il valore x1 a cui segue il valore x1 e cos` via fino al valore xN .
La probabilit`a p(x; N ) che xN = x si ottiene sommando p(x1 , x2 , . . . , xN ) su tutti i valori
possibili della sequenza x1 , x2 , . . . , xN 1 con la condizione xN = x:
X
p(xN = x; N ) =
p(x1 , x2 , . . . , xN = x)
x1 ,x2 ,...,xN 1
x1 ,x2 ,...,xN 1
X
X
p(x; N ) =
T (x0 x1 ) T (xN 2 x0 ) T (x0 x)
x0
x1 ,x2 ,...,xN 2
p(x0 ; N 1) T (x0 x)
x0
per ciascun nuovo numero generato avr`a sar`a un numero random con distribuzione di probabilit`a p(x; ).
543
(Rev. 2.0.2)
Il punto fondamentale del metodo Monte Carlo `e losservazione che le probabilit`a di transizione T (x x0 ) possono essere scelte in modo tale che la distribuzione asintotica coincida
con la distribuzione di probabilit`a p(x) voluta. Se questo `e il caso la distribuzione di frequenza
p(x; N ) dei valori di xN fornir`a per valori di N sufficientemente grandi una stima della distribuzione di probabilit`a p(x) mentre la sequenza xN +1 , xN +2 ,... fornir`a una sequenza di
numeri random distribuiti, con buona approssimazione, con la distribuzione di probabilit`
a
p(x). Chiaramente tanto pi`
u grande sar`a N tanto migliore sar`a la bont`a dei numeri generati.
Per determinare la forma delle probabilit`a di transizione affinch`e
lim p(x; N ) = p(x, ) p(x),
x0
X
=
p(x0 ; N ) T (x0 x) p(x, N ) T (x x0 ) .
x0
Questa relazione, nota come Master Equation, `e valida per ogni valore di N . Nel caso in cui
la variabile x possa assumere valori continui la somma va sostituita con un integrale.
Se nel limite di N molto grandi la distribuzione p(x; N ) converge alla distribuzione asintotica
p(x; ) = p(x) il termine a sinistra della Master Equation si annulla, e di conseguenza
deve annullarsi anche quello di destra se calcolato con p(x; N ) = p(x). Una condizione
sufficiente (ma non necessaria) affinch`e questo accada, e che quindi la distribuzione asintotica
della sequenza di Markov coincida con la distribuzione voluta p(x), `e che sia soddisfatta la
condizione detta di bilancio dettagliato:
p(x) T (x x0 ) = p(x0 ) T (x0 x),
ossia che una volta che la distribuzione ha raggiunto il valore asintotico la probabilit`a di
osservare nella sequenza levento x x0 sia la stessa di quella di osservare levento inverso
x0 x.
La condizione di bilancio dettagliato mette in relazione le probabilit`a di transizione T (x x0 )
con la distribuzione asintotica, ma non le specifica in maniera univoca poich`e ogni scelta delle
T (x x0 ) che soddisfi il bilancio dettagliato con una data p(x) avr`a come distribuzione
asintotica la p(x).
Una scelta semplice che soddisfa la relazione di bilancio dettagliato `e:
p(x0 )
T (x x0 ) = min 1,
.
p(x)
Questo vuol dire che se x0 `e pi`
u probabile di x allora T (x x0 ) = 1 e la transizione avviene
con probabilit`a 1. In caso contrario la probabilit`a della transizione `e data dal rapporto delle
probabilit`a di arrivo e di partenza per cui quanto pi`
u x0 `e poco probabile rispetto a x tanto
544
(Rev. 2.0.2)
pi`
u sar`a difficile la transizione. Questa scelta definisce il metodo Monte Carlo Metropolis.
Ovviamente altre scelte sono possibili. Osserviamo che usando il rapporto delle distribuzioni
di probabilit`a non `e necessario che questultime siano normalizzate.
Lalgoritmo di Metropolis `e facilmente realizzabile numericamente utilizzando i generatori di
numeri random con distribuzione uniforme in [0, 1]. La procedura `e la seguente:
1. Dato un valore xi della sequenza si sceglie un valore di prova x0 = xi + x dove, nel caso
di variabili continue, x `e un numero distribuito uniformemente nellintervallo [, ];
2. Si calcola il rapporto w = p(x0 )/p(xi );
3. Se w 1 si accetta il nuovo valore xi+1 = x0 ;
4. Se w < 1 si genera un numero random r con distribuzione uniforme in [0, 1];
5. Se r w si accetta il nuovo valore xi+1 = x0 ;
6. Se invece r > w il nuovo valore viene riggettato xi+1 = xi .
Per generare una sequenza di N elementi basta ripetere queste operazioni N volte a partire da
un valore iniziale x0 . In generale per avere una buona stima della p(x) `e necessario generare
sequenze molto lunghe.
La scelta del valore di pu`o essere cruciale. Infatti se `e troppo grande ci si muove troppo
e solo una piccola parte dei valori di prova proposti verr`a accettato. Se invece il valore di
`e troppo piccolo ci si muove poco e quasi tutti i valori di prova proposti saranno accettati.
In entrambi i casi, tuttavia, la stima p(x; N ) della distribuzione p(x) sar`a piuttosto cattiva.
Chiaramente piccolo o grande non `e indipendente dalla forma di p(x), per cui il valore di
va scelto di volta in volta. In genere si pu`o procedere per approssimazioni successive fino a
quando il valore di `e tale che circa da un terzo alla met`a dei valori di prova proposti viene
accettato. Anche la scelta del valore iniziale x0 pu`o essere cruciale. In generale conviene
partire da valori di x per i quali p(x) assume il valore massimo in modo da avere una buona
statistica delle zone pi`
u probabili con il minor numero possibile di estrazioni.
Nel caso di variabili discrete la scelta del valore di prova verr`a effettuato solo tra i valori
discreti possibili. Ad esempio se la variabile pu`o assumere solo valori interi n = 1, 1, 2, . . .
potremmo usare la regola:
1. Dato un valore xi della sequenza si sceglie un valore di prova x0 = xi + x dove x `e un
numero che assume con la stessa probabilit`a il valore x = 1 o x = 1.
Il seguente modulo genera una sequenza di Markov per una variabile continua x utilizzando
lalgoritmo Monte Carlo Metropolis con una distribuzione di probabilit`a p(x) qualsiasi.
Header: metropolis.h
/* ***************************************************************
545
(Rev. 2.0.2)
- Funzioni :
void init_metropolis ( unsigned int seed );
int metropolis ( double *x, double dx , double (* prob )( double x));
- Descrizione :
init_metropolis () -> Inizializza il generatore
metropolis ();
-> numero della catena di Markov
- $Id: metropolis .h v 1.1 24.10.04 AC
**************************************************************** */
extern void init_metropolis ( unsigned int seed ) ;
extern int metropolis ( double x , double dx , double ( prob ) ( double x ) ) ;
Modulo: metropolis.c
/* ***************************************************************
- Descrizione : Generatore Numeri Random con lalgoritmo
Monte Carlo Metropolis
Il generatore di numeri uniforme usato
e definito in
init_metropolis () e get_randu ()
- $Id: metropolis .c v 1.1 24.10.04 AC
**************************************************************** */
# include < s t d l i b . h>
# include " metropolis .h"
/*
* Funzioni private del modulo
*/
double get_randu ( void ) ;
546
(Rev. 2.0.2)
return 0 ;
}
/* ---* init_metropolis ()
*
* Inizilaizza il generatore utilizzato da Metropolis
*/
void init_metropolis ( unsigned int seed )
{
srand ( seed ) ;
return ;
}
/* ---* get_randu ()
*
* numero random uniforme in [0 ,1]
*/
double get_randu ( void )
{
return ( 1 . 0 rand ( ) / ( 1 . 0 + RAND_MAX ) ) ;
}
dove *x `e un puntatore alla variabile che in ingresso contiene il valore di xi mentre in uscita
quello di xi+1 . Il parametro dx contiene il valore di mentre la funzione prob(double x)
`e il puntatore alla funzione p(x). La funzione metropolis() ritorna 1 se il cambio `e stato
accettato e 0 nel caso contrario.
La funzione
void init_metropolis ( unsigned int seed )
inizializza la catena di Markov inizializzando con il seme seed il generatore di numeri random
con distribuzione uniforme utilizzato dalla funzione metropolis(), in questo caso la funzione
rand(). Se la catena di Markov non viene inizializzata esplicitamente questa viene inizializzata con il valore di default utilizzato dal generatore di numeri uniformi, in questo caso con
il valore 1 di seed.
Luso del modulo `e illustrato nellesempio seguente per generare numeri random con una
distribuzione Gaussiana.
Esempio: test-metropolis.c
/* ***************************************************************
547
(Rev. 2.0.2)
12345
2.5
"data.dat"
char argv [ ] )
/*
/*
/*
/*
Lunghezza sequenza
Lunghezza sequenza buttata
# cambi accettati
variabile della sequenza
*/
*/
*/
*/
if ( argc != 4 ) {
printf ( " Fornire parametri , please ... \n\n" ) ;
return 1 ;
}
x
= atof ( argv [ 1 ] ) ;
n_bad
= atoi ( argv [ 2 ] ) ;
n_number = atoi ( argv [ 3 ] ) ;
548
(Rev. 2.0.2)
}
per convertire i valori letti dalla linea di comando per trasformarli rispettivamente in valori
int e double.
Esercizi
1. Riportare su un grafico la funzione p(x) e p(x; N ) per un valore di N fissato al variare
di verificando che lapprossimazione migliore si ha quando il rapporto di accettazione
`e dellordine di 1/3 1/2. Utilizzare per p(x) una distribuzione Gaussiana ed una
esponenziale.
2. Calcolare i primi momenti della distribuzione p(x) utilizzando la funzione p(x; N ). Osserviamo che siccome la funzione p(x) non `e necessariamente normalizzata, i momenti
sono dati da:
n
hx i =
dx p(x) xn
R
'
dx p(x)
N
dx p(x; N ) xn
1 X n
R
=
xi
N
dx p(x; N )
i=1
549
(Rev. 2.0.2)
fm
M
M
m=1
m=1
Calcolare lerrore fatto nel calcolo dei primi due momenti di una distribuzione Gaussiana
ed una esponenziale sia ad N fissato in funzione di M sia ad M fissato in funzione di
N.
550
6. Random Walk
6.1. Random Walk in una dimensione: Caso Discreto
(Rev. 2.0.4)
Il modo pi`
u semplice di visualizzare un Random Walk `e quello di pensare ad un punto materiale che si muova lungo una retta spostandosi a caso in avanti oppure indietro di una distanza
fissata a ad intervalli di tempo discreti. Il suo moto, descritto da una serie di spostamenti
discreti casuali di ampiezza a in avanti e indietro, `e chiamato Random Walk discreto in una
dimensione.
Se ad ogni intervallo di tempo il punto deve muoversi, ossia non pu`o scegliere di restare nella
posizione in cui si trova, il Random Walk `e descritto dalla probabilit`a p+ di andare avanti e
dalla probabilit`a p di andare indietro. Le due probabilit`a possono differire tra loro ma in
ogni caso si deve avere p + p+ = 1.1
Se il punto non ha nessuna preferenza nella direzione da prendere il Random Walk `e chiamato
simmetrico ed ad ogni passo la probabilit`a p+ di andare avanti e la probabilit`a p di andare
indietro sono uguali e valgono p+ = p = 1/2. Nel seguito considereremo in dettaglio questo
caso. Lestensione al caso non simmetrico con p+ 6= p `e tuttavia relativamente semplice.
Siccome gli spostamenti sono discreti la posizione x del punto lungo la retta pu`o essere
identificata con i numeri interi x = 0, 1, 2, . . . che, se associamo x = 0 con lorigine sulla
retta, indicano la distanza dallorigine in unit`a di spostamento elementare a.
Il Random Walk `e un processo aleatorio nel senso che cammini differenti che partono tutti
dalla stessa posizione, ad esempio lorigine x = 0, in generale si troveranno dopo uno stesso
numero di passi N in posizioni x diverse. Misurando la frequenza con cui i cammini finiscono
nella posizione x dopo N passi possiamo definire la probabilit`a P (x, N ) che un punto che si
muova con un Random Walk su una linea si trovi nella posizione x dopo aver fatto N passi.
Chiaramente P (x, N ) dipende dalla posizione di partenza del punto che fissa il valore di
P (x, 0). Senza perdere di generalit`a possiamo assumere che allistante iniziale il punto si
trovi nellorigine x = 0 cosicch`e P (x, 0) `e semplicemente
P (x, 0) = x,0
dove x,0 `e la delta di Kroneker che vale 1 se x = 0 e zero altrimenti.
Per trovare lequazione che determina P (x, N ) una volta nota P (x, 0) osserviamo che il moto
casuale si ripete con le stesse modalit`a in ogni posizione lungo la retta. Inoltre siccome il
punto non pu`o restare fermo ma deve muoversi ad ogni passo, `e chiaro che per trovarsi in
1
Se il punto pu`
o restare fermo si deve considerare anche la probabilit`
a p0 di rimanere nel punto in cui si
trova. In questo caso p + p+ < 1 ma ovviamente p + p+ + p0 = 1 perch`e il punto necessariamente andr`
a
avanti o indietro o rimarr`
a fermo.
551
(Rev. 2.0.4)
una posizione con x dispari dovr`a aver fatto un numero totale di passi N dispari, mentre per
trovarsi su una posizione con x pari il numero di passi N dovr`a necessariamente essere pari.
In altre parole x ed N devono avere la stessa parit`a. Di conseguenza con N 1 passi il punto
pu`o trovarsi nelle posizioni x 1 ma non nella posizione x per cui la probabilit`a di trovare il
punto in x con N passi `e uguale alla probabilit`a che il punto si trovi in x 1 con N 1 passi
u la probabilit`a che il punto si trovi in x + 1 con N 1 passi
e che scelga di andare avanti, pi`
e che scelga di andare indietro:
p
+
x1
P(x1,N1)
x
P(x,N)
p
+
x+1
P(x+1,N1)
(6.1)
Osserviamo che il Random Walk `e un processo di Markov. Infatti nella notazione utilizzata
nella discussione del metodo Monte Carlo si ha
T (x x + 1) p+
T (x x0 )
T (x x 1) p
per cui si riconosce facilmente che lequazione per P (x, N ) non `e altro che lequazione
p(x; N ) =
x0
552
(Rev. 2.0.4)
prendendo, se necessario, alla fine solo la parte reale ovvero aggiungendogli il suo complesso
coniugato. Sostituendo questa espressione di P (x, N ) nellequazione (6.1) e dividendo per
eix si ottiene lequazione per W (, N ):
ei
e+i
W (, N 1) +
W (, N 1) = cos W (, N 1),
2
2
Questa equazione pu`o facilmente essere iterata scrivendo W (, N 1) in funzione di W (, N
2), W (, N 2) in funzione di W (, N 3) e cos` via fino ad ottenere W (, N ) in funzione
di W (, 0):
W (, N ) =
W (, N ) = cos W (, N 1)
= (cos )2 W (, N 2)
=
= (cos )N W (, 0)
Siccome ad ogni valore di corrisponde una soluzione e lequazione (6.1) `e lineare, la soluzione
generale dellequazione si ottiene sommando su tutte le possibili soluzioni, ovvero su tutti i
valori possibili di :
Z 2
1
P (x, N ) =
d eix (cos )N W (, 0)
N 0
dove N `e un fattore di normalizzazione che assicura la regola di somma
X
P (x, N ) = 1,
N
(6.2)
per x intero 6= 0
si ha
Z
d ein = 2 n,0
per cui si verifica facilmente che la W (, 0) = N /(2) fornisce la soluzione cercata. Sostituendo questa espressione nella soluzione generale otteniamo infine la soluzione
Z 2
1
P (x, N ) =
d eix (cos )N
2 0
Z 2
1
=
(6.3)
d cos(x) (cos )N
2 0
Osserviamo che la somma su tutti i possibili valori di rende nulla la parte immaginaria
dellintegrale. Questo non deve sorprendere perch`e `e facile mostrare che sommare su tutti
i valori di in [0, 2] `e equivalente a sommare ad eix W (, N ) per ogni valore di il suo
complesso coniugato.
553
(Rev. 2.0.4)
Caso N 1
Nel limite N 1 lintegrale nellequazione (6.3) pu`o essere valutato osservando che essendo
| cos | 1 il contributo dominante allintegrale per N 1 `e dato dai valori di per cui si
ha | cos | ' 1, come mostra la figura seguente per N = 6:
(cos )
0.8
0.6
0.4
0.2
0
/4
/2
3/4
Per valutare lintegrale dobbiamo sommare su tutti i valori di nellintervallo [0, 2] per cui
`e facile convincersi che il contributo dominante allintegrale viene dai valori di nellintorno
dei punti
= 0, , 2
e quindi per N 1 possiamo valutare lintegrale come
Z 2
Z
Z +
Z
d
d +
d +
0
2
554
2
2
' e /2
2
(Rev. 2.0.4)
0.8
2
exp(-6 /2)
0.6
exp(-6 /2)
0.4
ln (cos )
(cos )
0.1
0.01
0.2
/8
/4
3/8
0.001
0
/8
/4
3/8
Come si vede chiaramente da queste figure gi`a per questo valore di N lapprossimazione
(6.4) fornisce unottima stima dellintegrale nellintervallo [0, ].
' 2
Scegliendo anche in questo caso un valore di sufficientemente piccolo ( 1) possiamo
valutare lintegrale con un procedimento analogo a quello utilizzato per ' 0. Infatti
se `e piccolo nellintervallo [2 , 2] possiamo scrivere
cos ' 1
per cui con argomenti simili a quelli
Z 2
deix (cos )N '
2
=
'
( 2)2
2
' e(2) /2
2
utilizzati per ' 0, si ha
Z 2
N ( 2)2
d exp ix
2
2
Z 0
N 2
i2x
e
d exp ix
2
Z 0
2
N
d exp ix
,
N 1,
2
(6.5)
555
(Rev. 2.0.4)
N 1.
(6.7)
Il fattore 2 `e dovuto al fatto che per un dato N solo le posizioni con x della stessa parit`a (pari
o dispari) sono accessibili. Quindi nella somma (6.2) solo la met`a dei punti contribuisce.
In conclusione nel limite N 1 la distribuzione di probabilit`a del Random Walk discreto
unidimensionale simmetrico diventa una Gaussiana con media nulla e varianza uguale a N .
Questo implica che hx2 i, il valore quadratico medio della posizione x, cresce linearmente con
N per cui il moto per N 1 `e diffusivo.
556
(Rev. 2.0.5)
Esercizi
1. Scrivere un programma che calcola numericamente lintegrale (6.3) e confrontarne il
risultato con quello ottenuto dalla formula asintotica (6.7) per vari valori di N .
2. Scrivere un programma che simuli un Random Walk unidimensionale discreto simmetrico e calcolare la probabilit`a P (x, N ) generando Nesp cammini di N passi con cui
calcolare la frequenza con cui un punto che parta dallorigine si trover`a nella posizione
x dopo N passi. Confrontare la distribuzione ottenuta con le espressioni teoriche (6.3)
e (6.7).
3. Scrivere un programma che simula un Random Walk unidimensionale discreto simmetrico e calcolare hx2 i in funzione di N verificando la legge di proporzionalit`a diretta.
(Rev. 2.0.5)
hx in =
n
X
x=n
nX
walk
1
x P (x, n) :=
xn (i)k ,
n walk
k
n = 1, . . . , N, k = 1, 2
i=1
/* ***************************************************************
- Descrizione :
Simula un random walk discreto in 1 dimensione
di step = 1, 2,... , n_step passi
Calcola Distribuzione probabilita P(x, n_step )
Calcola <x> e <x^2> in funzione del numero step
557
(Rev. 2.0.5)
di passi fatti.
- Input :
Numero massimo di passi per walk
Numero di walks per la statistica
- Output :
File FILE_PR -> x P(x, n_step )
File FILE_ME -> step <x>( step) <x^2 >( step)
- Parametri :
FILE_PR : File output probabilita
FILE_ME : File output medie
- $Id: rwd_1d .c v 1.3 08.11.04 AC
**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
# include <math . h>
/* ============================================================== */
/*
Macros
*/
/* ============================================================== */
# define FILE PR " walk_pr .dat"
/* File probabilita */
# define FILE ME " walk_me .dat"
/* File momenti
*/
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
int
ivect ( int n ) ;
double dvect ( int n ) ;
FILE
open_file ( const char path , const char mode ) ;
int main ( void )
{
register
int step ;
register
int walk ;
int
n_step ;
int
n_walk ;
int
pos ;
int
prob ;
unsigned int
seed ;
double
x1 , x2 ;
double
r , norm ;
FILE pf_pr , pf_x2 ;
char
line [ 8 1 ] ;
/*
/*
/*
/*
/*
/*
/*
/*
/* output files
/* input buffer
558
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
(Rev. 2.0.5)
559
(Rev. 2.0.5)
560
(Rev. 2.0.5)
che alloca uno spazio di memoria contiguo per n oggetti di tipo int e ritorna il puntatore al primo oggetto. Nel caso non sia possibile allocare la memoria viene scritto
un messaggio di errore sullo stderr ed il programma termina con il codice di uscita
EXIT FAILURE definito nel file di header stdlib.h, usualmente come 1.
Il puntatore allarray `e spostato di n step posizioni in modo che lindice degli elementi
dellarray vada da -n step per il primo elemento a n step per lultimo, e coincida cos`
con la posizione x.
Visto che solo le posizioni con x della stessa parit`a di n step sono accessibili si sarebbe
potuto usare unarray dimensione pari alla met`a, pi`
u uno se N `e pari. In questo modo
si utilizza il doppio della memoria ma in compenso si ha un controllo sui risultati perch`e
nelle posizioni con x di parit`a diversa n step la probabilit`a deve essere identicamente
nulla. Inoltre la richiesta di memoria non `e eccessiva in quanto la probabilit`a che un
walk di N passi finisca nella posizione x = N `e uguale a 1/2N per cui per osservare
un tale evento servono un numero di walks n walk dellordine di 2N , ad esempio per
N = 20 servono O(106 ) walks. Di conseguenza il valore di N non `e mai troppo grande
e quindi la richiesta di memoria `e piuttosto limitata.
561
(Rev. 2.0.5)
= dvect ( n_step + 1 ) ;
= dvect ( n_step + 1 ) ;
x2
Vi sono sostanzialmente due modi di fare la statistica in funzione del numero di passi,
ossia del tempo. Nel primo si portano avanti gli n walk walks in parallelo facendo fare
un passo a tutti i walks ed effettuando poi la statistica sulle nuove posizioni di tutti i
walks. Loperazione viene ripetuta fino a quando tutti i walks hanno effettuato n step
passi. Questo metodo ovviamente richiede di tenere in memoria la posizione istantanea
di tutti gli n walk walks in modo da poter poi effettuare il prossimo passo dellevoluzione
per ciascun walk.
Il secondo metodo `e il complementare del precedente: si fa evolvere un solo walk alla
volta fino al numero di passi massimo n step accumulando ad ogni passo su variabili
di appoggio le varie grandezze di interesse per la statistica. Il procedimento viene poi
ripetuto per tutti gli n walk walks. Questo metodo richiede di tenere in memoria le
variabili di appoggio, una o pi`
u, per ogni passo.
Quale dei due metodi sia preferibile dipende in generale dal tipo di problema da risolvere,
in questo caso il secondo metodo `e migliore perch`e richiede una quantit`a di memoria
pi`
u limitata.
Per calcolare i primi due momenti hxi ed hx2 i in funzione del numero di passi servono
due variabili di appoggio per accumulare ad ogni passo n il valore della posizione xn (i)
delli-esimo walk ed del suo quadrato xn (i)2 . A questo scopo si usano i due arrays x1 e
x2 che per comodit`a sono presi di dimensione n step + 1 in modo che lindice dellarray
corrisponda al passo: indice 0 valori istante iniziale, indice 1 valori dopo in passo, indice
2 valori dopo due passi e cos` via. Gli arrays sono creati utilizzando la funzione dvect()
simile alla funzione ivect() che alloca uno spazio di memoria contiguo per un array di
oggetti di tipo double e ritorna il puntatore al primo oggetto dellarray.
for ( step = 1 ; step <= n_step ; ++step ) {
r
= rand ( ) / ( RAND_MAX + 1 . 0 ) ;
pos
+= ( r < 0 . 5 ? 1 : 1 ) ;
x1 [ step ] += pos ;
x2 [ step ] += ( pos pos ) ;
}
562
(Rev. 2.0.4)
...
}
/* numero di walks di n_step passi in pos */
++prob [ pos ] ;
}
Questo `e il ciclo sui vari walks per la statistica. Il ciclo `e ripetuto n walk volte. Prima
di iniziare un nuovo walk la posizione pos viene rimessa nellorigine pos = 0. Alla fine
del walk di n step il punto si trover`a nella posizione pos per cui viene aumentato di 1 il
valore dellelemento di indice pos dellarray prob. Alla fine della simulazione lelemento
di indice i dellarray prob conterr`a il numero di walks che sono finiti nella posizione
x=i dopo N =n steps passi, che normalizzato con il numero totale di walks n walks
della simulazione fornisce la frequenza cercata.
Esercizi
1. Studiare la distribuzione di probabilit`a per walks di n step = 5, 10, 20 e 50 passi usando
n walk = 10000 walks. Come cambiano le curve? Spiegare il comportamento osservato
stimando il numero di walks necessario nei vari casi per risolvere numericamente tutta
la distribuzione.
2. Studiare i primi due momenti della distribuzione di probabilit`a per walks di n step = 5,
10, 20 e 50 passi usando n walk = 10000 walks. Spiegare il diverso comportamento dei
momenti e delle distribuzioni di probabilit`a stimati entrambi numericamente al variare
del numero di passi del walk per numero di walks totale fissato.
3. Modificare il programma per simulare un Random Walk discreto con p 6= p+ e graficare
le curve hxi, hx2 i e hx2 i hxi2 in funzione del numero di passi N = 1, . . . , n step.
Spiegare il comportamento osservato.
4. Modificare il programma per simulare un Random Walk discreto con p 6= p+ e calcolare
la distribuzione di probabilit`a P (x, N ) per vari valori di N . Riportando i valori ottenuti
su un grafico cercare di dedurre la forma della distribuzione di probabilit`a ottenuta.
d cos(x) (cos )N
563
(Rev. 2.0.4)
x2
2
exp
P (x, N ) =
,
2N
2N
/* ***************************************************************
- Descrizione :
Calcola la distribuzione teorica P(pos , n_step ) di
un random walk discreto simmetrico in 1 dimensione
e la confronta con il risultato asintiotico valido
nel limite n_step >> 1:
P_g(pos , n_step ) = (2/ PI n_step )^1/2
exp(-pos ^2/2 n_step )
- Input :
Numero di passi n_step massimo
- Output :
FILE_PR -> pos P(pos , n_step ) P_g(pos , n_step )
- Parametri :
N_DIV
: # intervalli integrazione
FILE_PR : File output
- Moduli :
integ.c
564
(Rev. 2.0.4)
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
double f_real ( double x ) ;
/* parte rea. integrando */
double f_imag ( double x ) ;
/* parte imm. integrando */
double gauss ( double x , double v ) ;
/* Gaussiana
*/
/* ============================================================== */
/*
variabili globali
*/
/* ============================================================== */
struct wlk {
int
x;
/* posizione walk
*/
int steps ;
/* # passi del walk */
} Walk ;
int main ( void )
{
int
pos ;
int
intervalli ;
double i_real , i_imag ;
double
pr_asi ;
double pi2 = 2 . ( double ) PI ;
char
line [ 8 1 ] ;
FILE
pf ;
/*
/*
/*
/*
posizione x walk
# intervalli integrazione
P. reale/ immaginaria integ
P asintotica
/* input buffer
*/
*/
*/
*/
*/
intervalli = N_DIV ;
/* Parametri */
printf ( "\n Quanti passi in totale ? " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" ,&Walk . steps ) ;
pf = fopen ( FILE_PR , "w" ) ;
if ( pf == NULL ) {
fprintf ( stderr , "\n\ nErrore in apertura di %s\n" , FILE_PR ) ;
exit ( EXIT_FAILURE ) ;
}
else {
printf ( "\n Probabilita su file: %s\n\n" , FILE_PR ) ;
}
fprintf ( pf , "# x
P_rea
P_asi
diff" ) ;
fprintf ( pf , "
diff %%
P_imm \n" ) ;
for ( pos = Walk . steps ; pos <= Walk . steps ; ++pos ) {
Walk . x = pos ;
i_real = integ_trap ( 0 . , pi2 , intervalli , f_real ) / pi2 ;
i_imag = integ_trap ( 0 . , pi2 , intervalli , f_imag ) / pi2 ;
pr_asi = 2 . 0 gauss ( ( double ) pos , Walk . steps ) ;
fprintf ( pf , "%4d
pos ,
i_real ,
%8.6f
%8.6f
%9.2e
%6.2f
%9.2e\n" ,
565
(Rev. 2.0.4)
pr_asi ,
( pr_asi i_real ) ,
1 0 0 ( pr_asi i_real ) / pr_asi ,
i_imag
);
}
return 0 ;
}
double f_real ( double x )
{
double c ;
c = cos ( x ( double ) Walk . x ) ;
c = pow ( cos ( x ) , ( double ) Walk . steps ) ;
return c ;
}
double f_imag ( double x )
{
double c ;
c = sin ( x ( double ) Walk . x ) ;
c = pow ( cos ( x ) , ( double ) Walk . steps ) ;
return c ;
}
double gauss ( double x , double var )
{
static double pi2 = 2 . 0 ( double ) PI ;
double c ;
c = 0 . 5 x x / var ;
return ( sqrt ( 1 . 0 / ( pi2 var ) ) exp(c ) ) ;
}
/* posizione walk
*/
/* # passi del walk */
per cui non `e possibile passare i valori di x ed N direttamente come parametri delle
funzioni da integrare. Questi vengono forniti alle funzioni tramite la struttura a scopo
566
(Rev. 2.0.4)
globale Walk. Lidentificatore della struttura `e scritto con la prima lettera maiuscola
per indicare che il suo scopo `e globale.
Il programma viene compilato come
$ cc rwd_1dteo . c integ . c lm
ovvero come
$ cc c rwd_1dteo . c
$ cc c integ . c
$ cc rwd_1dteo . o integ . o lm
Quando viene eseguito i valori sono scritti nel file walk teo.dat. Ad esempio per N = 10 si
ha
# x
10
9
8
7
6
5
4
3
2
1
0
1
2
3
4
5
6
7
8
9
10
P rea
0.000977
0.000000
0.009766
0.000000
0.043945
0.000000
0.117188
0.000000
0.205078
0.000000
0.246094
0.000000
0.205078
0.000000
0.117188
0.000000
0.043945
0.000000
0.009766
0.000000
0.000977
P asi
0.001700
0.004396
0.010285
0.021773
0.041707
0.072289
0.113372
0.160882
0.206577
0.240008
0.252313
0.240008
0.206577
0.160882
0.113372
0.072289
0.041707
0.021773
0.010285
0.004396
0.001700
diff
7 . 2 4 e04
4 . 4 0 e03
5 . 1 9 e04
2 . 1 8 e02
2.24e03
7 . 2 3 e02
3.82e03
1 . 6 1 e01
1 . 5 0 e03
2 . 4 0 e01
6 . 2 2 e03
2 . 4 0 e01
1 . 5 0 e03
1 . 6 1 e01
3.82e03
7 . 2 3 e02
2.24e03
2 . 1 8 e02
5 . 1 9 e04
4 . 4 0 e03
7 . 2 4 e04
diff %
42.56
100.00
5.05
100.00
5.37
100.00
3.37
100.00
0.73
100.00
2.46
100.00
0.73
100.00
3.37
100.00
5.37
100.00
5.05
100.00
42.56
P imm
3.49e15
4.41e15
5.80e15
7.08e15
8.05e15
8.82e15
8.92e15
8.16e15
5.97e15
3.28e15
0 . 0 0 e+00
3 . 2 8 e15
5 . 9 7 e15
8 . 1 6 e15
8 . 9 2 e15
8 . 8 2 e15
8 . 0 5 e15
7 . 0 8 e15
5 . 8 0 e15
4 . 4 1 e15
3 . 4 9 e15
da cui si vede che gi`a per N = 10 la formula asintotica fornisce unottima stima dellintegrale.
La simulazione numerica di un Random Walk Discreto in due dimensioni non presenta particolari difficolt`a rispetto al caso unidimensionale. La differenza principale `e nel fatto che in
due dimensioni vi sono quattro direzioni possibili:
567
(Rev. 2.0.4)
(x,y+1)
p
u
pl
pr
(x1,y)
(x,y)
(x+1,y)
pd
(x,y1)
con le relative probabili`a: pr passo a destra, pu passo in alto, pl passo a sinistra e pd passo in
basso. In questo caso il modo pi`
u semplice per effettuare la scelta della direzione consiste nel
dividere lintervallo [0, 1] in quattro parti di lunghezze pari alle probabilit`a ed utilizzare una
variabile variabile r con distribuzione uniforme nellintervallo [0,1] per effettuare la scelta:
r
r
r
r
[0, pr )
[pr , pr + pu )
[pr + pu , pr + pu + pl )
[pr + pu + pl , 1)
destra
alto
sinistra
basso
(x, y) (x + 1, y)
(x, y) (x, y + 1)
(x, y) (x 1, y)
(x, y) (x, y 1)
Il seguente programma illustra questo metodo nel caso di un Random Walk Discreto bidimensionale simmetrico, pr = pu = pl = pd = 1/4, racchiuso in una dominio quadrato
[L, L] [L, L] di dimensioni (2L + 1) (2L + 1). Il programma in realt`a richiede di
specificare solo tre delle quattro probabilit`a, la quarta `e determinata dalla condizione di normalizzazione pr + pu + pl + pd = 1. Come grandezze caratteristiche del walk vengono calcolati
i primi due momenti h(x x0 )i, h(y y0 )i, h(x x0 )2 i ed h(y y0 )2 i dello spostamento relativo
nelle due direzioni dal punto di partenza (x0 , y0 ) in funzione del numero di passi effettuati.
Le medie vengono calcolate generando una sequenza di random walks indipendenti tutti della
stessa lunghezza e originati dal punto (x0 , y0 ), e mediando su di essi le grandezze volute.
Il programma prende come input il numero massimo di passi dei walks ed il numero di walks
indipendenti da generare per calcolare le medie. La dimensione lineare del dominio quadrato
`e fissata tramite la macro L. Loutput `e su due files specificati dalle macros FILE ME e FILE CF
e contengono rispettivamente i momenti in funzione del numero di passi e la traiettoria dei
primi N CF walks generati.
Programma: rwd 2d.c
/* ***************************************************************
- Descrizione :
Simula un random walk discreto in 2 dimensione
di step = 1, 2,... , n_step passi su un reticolo
568
(Rev. 2.0.4)
step ;
n_step ;
walk ;
n_walk ;
/*
/*
/*
/*
numero
valore
indice
numero
*/
*/
*/
*/
569
int
pos_x , pos_y ;
int
pos_x0 , pos_y0 ;
int
walks ;
double
p_r ;
double
p_l ;
double
p_u ;
double
double
int
x1 , x2 ;
y1 , y2 ;
im1 , ip1 ;
(Rev. 2.0.4)
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
unsigned int
seed ;
double
p_step_r ;
double
p_step_l ;
double
p_step_u ;
double
xx , r , norm ;
FILE
pf_me ;
FILE
pf_cf ;
char
line [ 8 1 ] ;
/* Parametri */
printf ( " Reticolo Quadrato
: ");
printf ( " %d <= x <= %d \t %d <= y <= %d \n\n" ,
L , L , L , L ) ;
printf ( " Numero massimo di passi del walk : " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" ,& n_step ) ;
printf ( " Quanti walks per la statistica
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" ,& n_walk ) ;
: ");
/* Probabilita */
p_r = 0 . 2 5 ;
/* passo a destra
x -> x + 1 */
p_u = 0 . 2 5 ;
/* passo in su
y -> y + 1 */
p_l = 0 . 2 5 ;
/* passo a sinistra x -> x - 1 */
/* Condizioni al bordo */
ip1 = ivect ( 2 L + 1 ) + L ;
im1 = ivect ( 2 L + 1 ) + L ;
#if 1
printf ( " Condizione al bordo
set_free_bc ( ip1 , im1 , L , L ) ;
# endif
#if 0
printf ( " Condizione al bordo
set_refl_bc ( ip1 , im1 , L , L ) ;
# endif
570
: free\n" ) ;
: reflecting \n" ) ;
*/
*/
*/
*/
*/
*/
*/
*/
*/
*/
#if 0
printf ( " Condizione al bordo
set_peri_bc ( ip1 , im1 , L , L ) ;
# endif
(Rev. 2.0.4)
: peridodic \n" ) ;
/* seed */
seed = 1 2 3 4 5 ;
/* Apertura Files */
pf_cf = open_file ( FILE_CF , "w" ) ;
printf ( "%3d walks su file
: %s\n\n" , FILE_ME ) ;
/* Posizione iniziale */
pos_x0 = 0 ;
pos_y0 = 0 ;
/* Definizione ed inizializzazione vettori statistica */
walks = ivect ( n_step + 1 ) ;
x1
= dvect ( n_step + 1 ) ;
x2
= dvect ( n_step + 1 ) ;
y1
= dvect ( n_step + 1 ) ;
y2
= dvect ( n_step + 1 ) ;
for ( step = 0 ; step <= n_step ; ++step ) {
x1 [ step ]
= 0.0;
x2 [ step ]
= 0.0;
y1 [ step ]
= 0.0;
y2 [ step ]
= 0.0;
walks [ step ] = 0 ;
}
/* divisione intervallo [0 ,1] in quattro parti */
p_step_r = p_r ;
p_step_u = p_step_r + p_u ;
p_step_l = p_step_u + p_l ;
/* Inizializzazione generatore di numeri random */
srand ( seed ) ;
/* Partenza dei walks */
for ( walk = 1 ; walk <= n_walk ; ++walk ) {
/* Posizione di partenza del walk */
step
= 0;
pos_x
= pos_x0 ;
pos_y
= pos_y0 ;
x1 [ step ] += pos_x ;
x2 [ step ] += ( pos_x pos_x ) ;
y1 [ step ] += pos_y ;
y2 [ step ] += ( pos_y pos_y ) ;
571
(Rev. 2.0.4)
++walks [ step ] ;
for ( step = 1 ; step <= n_step ; ++step ) {
r = rand ( ) / ( RAND_MAX + 1 . 0 ) ;
if ( r < p_step_r ) {
/* 0 <= r < p_r : x -> x + 1
*/
pos_x = ip1 [ pos_x ] ;
if ( pos_x > L ) break ;
}
else if ( r < p_step_u ) {
/* p_r <= r < p_u : y -> y + 1 */
pos_y = ip1 [ pos_y ] ;
if ( pos_y > L ) break ;
}
else if ( r < p_step_l ) { /* p_u <= r < p_l : x -> x - 1 */
pos_x = im1 [ pos_x ] ;
if ( pos_x < L ) break ;
}
else {
/* p_l <= r < 1 : y -> y - 1
*/
pos_y = im1 [ pos_y ] ;
if ( pos_y < L ) break ;
}
if ( walk <= N_CF )
fprintf ( pf_cf , "
}
++walks [ step ] ;
xx
= pos_x
x1 [ step ] += xx ;
x2 [ step ] += ( xx
{
%d %d %d\n" , walk , pos_x , pos_y ) ;
pos_x0 ;
xx ) ;
xx
= pos_y pos_y0 ;
y1 [ step ] += xx ;
y2 [ step ] += ( xx xx ) ;
}
}
/* Statistica */
fprintf ( pf_me , "# n\t<x >\t<y >\t<x^2 >\t<y^2 >\ tfrac walks\n" ) ;
for ( step = 0 ; step <= n_step ; ++step ) {
if ( walks [ step ] != 0 ) {
norm = 1 . 0 / ( double ) ( walks [ step ] ) ;
fprintf ( pf_me , "%d\t%f\t%f\t%f\t%f\t%f\n" , step ,
( double ) x1 [ step ] norm ,
( double ) y1 [ step ] norm ,
( double ) x2 [ step ] norm ,
( double ) y2 [ step ] norm ,
( double ) walks [ step ] / ( double ) n_walk ) ;
}
}
return 0 ;
}
572
(Rev. 2.0.4)
/*
* free_bc
*/
void set_free_bc ( int ip1 , int im1 , int i_min , int i_max )
{
int i ;
for ( i = i_min ; i<= i_max ; ++i ) {
ip1 [ i ] = i + 1 ;
im1 [ i ] = i 1 ;
}
return ;
}
/*
* reflecting_bc
*/
void set_refl_bc ( int ip1 , int im1 , int i_min , int i_max )
{
int i ;
for ( i = i_min ; i<= i_max ; ++i ) {
ip1 [ i ] = i + 1 ;
im1 [ i ] = i 1 ;
}
ip1 [ i_max ] = i_max 1 ;
im1 [ i_min ] = i_min + 1 ;
return ;
}
/*
* periodic_bc
*/
void set_peri_bc ( int ip1 , int im1 , int i_min , int i_max )
{
int i ;
for ( i = i_min ; i<= i_max ; ++i ) {
ip1 [ i ] = i + 1 ;
im1 [ i ] = i 1 ;
}
ip1 [ i_max ] = i_min ;
im1 [ i_min ] = i_max ;
return ;
}
/* ---* ivect ()
*/
573
(Rev. 2.0.4)
574
(Rev. 2.0.4)
del dominio. In questi casi si deve decidere cosa fare. Questo problema va sotto il nome di
scelta delle condizioni al bordo. Differenti scelte sono possibili. Questo programma considera
le tre scelte pi`
u comuni:
Condizioni libere al bordo (free boundary condition). In questo caso se il walk arriva al
bordo esce dal dominio e quindi si ferma.
Condizioni riflettenti al bordo (reflecting boundary condition). In questo caso il bordo
si comporta come delle pareti elastiche per cui se il walk arriva al bordo viene riflesso
allinterno del dominio.
Condizioni periodiche al bordo (periodic boundary condition). In questo caso se il walk
arriva al bordo esce dal dominio ma rientra nel punto simmetrico dalla parte opposta
Punti Simmetrici
Vi sono differenti modi di imporre queste condizioni al bordo. Il programma utilizza delle
Tavole dei Vicini realizzate mediante i due arrays ip1 e im1 i cui elementi di indice i contengono rispettivamente la coordinata del vicino a destra (i+1) e quella del vicino a sinistra
(i-1). Di conseguenza listruzione
pos_x = ip1 [ pos_x ] ;
in modo tale che gli indici degli arrays varino anchessi tra L ed L. In questo modo infatti
ip1[-L] corrisponde al primo elemento dellarray mentre ip1[L] allultimo, ossia allelemento
2*L+1-esimo. Questo accorgimento ci permette di identificare lindice degli arrays con le
575
(Rev. 2.0.4)
posizioni sul reticolo e viceversa. Gli arrays sono creati utilizzando la funzione ivect() che
alloca uno spazio di memoria contiguo per un array oggetti di tipo int e ritorna il puntatore
al primo oggetto dellarray.
I valori contenuti negli arrays ip1 e im1 dipende dal tipo di condizione al bordo utilizzato e
vengono assegnati dalle funzioni
void set_free_bc ( int ip1 , int im1 , int i_min , int i_max ) ;
void set_refl_bc ( int ip1 , int im1 , int i_min , int i_max ) ;
void set_peri_bc ( int ip1 , int im1 , int i_min , int i_max ) ;
dove ip1 e im1 sono i puntatori agli arrays dei vicini e i min e i max sono il valore minimo e
massimo dellindice i dellarray, ovvero della coordinata sul reticolo.
Funzione set free bc()
Questa funzione fissa le condizioni libere al bordo. In questo caso
ip1 [ i ] = i + 1 ;
im1 [ i ] = i 1 ;
per ogni valore di i. Sar`a poi cura del programma prendere le dovute azioni, ossia fermare il
walk, quando questo raggiunge il bordo.
Funzione set refl bc()
Questa funzione fissa le condizioni riflettenti al bordo. In questo caso
ip1 [ i ] = i + 1 ;
Analogamente
im1 [ i ] = i 1 ;
576
(Rev. 2.0.4)
Algoritmo di evoluzione
Per decidere la mossa da fare si estrae un numero random r con distribuzione uniforme in
[0, 1) e si determina in quale dei quattro intervalli di lunghezza pr , pu , pl e pd in cui `e stato
diviso lintervallo unitario cade. I quattro intervalli sono in successione separati dai punti
p_step_r = p_r ;
p_step_u = p_step_r + p_u ;
p_step_l = p_step_u + p_l ;
Statistica
Vi sono sostanzialmente due modi di fare la statistica in funzione del tempo. Nel primo si
portano avanti tutti i walks in parallelo e si effettua la statistica ad ogni passo. Il secondo
metodo `e il complementare in cui si fa evolvere un solo walk alla volta fino al numero di
passi massimo voluto accumulando ad ogni passo su variabili di appoggio le grandezze con cui
effettuare la statistica una volta finita levoluzione di tutti walks. Come nel caso del Random
Walk unidimensionale anche in questo caso il secondo metodo `e preferibile perch`e richiede
una quantit`a di memoria pi`
u limitata.
Dovendo calcolare i primi due momenti dello spostamento lungo le direzioni x e y si devono
utilizzare quattro arrays x1, x2, y1 e y2 di dimensione pari al numero massimo di passi dei
walks n step
577
=
=
=
=
x1
x2
y1
y2
dvect ( n_step
dvect ( n_step
dvect ( n_step
dvect ( n_step
+
+
+
+
(Rev. 2.0.4)
1);
1);
1);
1);
su cui accumulare ad ogni passo le coordinate x e y di ciascun walk ed dei loro quadrati
x2 e y 2 . Gli arrays sono creati utilizzando la funzione dvect() che alloca uno spazio di
memoria contiguo per un array di oggetti di tipo double e ritorna il puntatore al primo
oggetto dellarray.
Per comodit`a gli arrays sono presi di dimensione n step + 1 in modo che lindice corrisponda
al passo
for ( walk = 1 ; walk <= n_walk ; ++walk ) {
/* Posizione di partenza del walk */
step
= 0;
pos_x
= pos_x0 ;
pos_y
= pos_y0 ;
x1 [ step ] += pos_x ;
x2 [ step ] += ( pos_x pos_x ) ;
y1 [ step ] += pos_y ;
y2 [ step ] += ( pos_y pos_y ) ;
++walks [ step ] ;
for ( step = 1 ; step <= n_step ; ++step ) {
...
++walks [ step ] ;
xx
= pos_x pos_x0 ;
x1 [ step ] += xx ;
x2 [ step ] += ( xx xx ) ;
xx
= pos_y pos_y0 ;
y1 [ step ] += xx ;
y2 [ step ] += ( xx xx ) ;
}
}
Nel caso di condizioni libere al bordo non tutti i walks riescono ad effettuare il numero
massimo di passi n step richiesto e di conseguenza per effettuare correttamente la statistica
per ogni passo step si deve tenere il conto del numero di walks che hanno effettuato step
passi. Nel programma questo `e ottenuto utilizzando larray di contatori walks
walks = ivect ( n_step + 1 ) ;
il cui elemento walks[step] viene incrementato di 1 ogni volta che un walk raggiunge il passo
step. Alla fine della simulazione lelemento di indice i dellarray walks conterr`a il numero di
walks che nella simulazione hanno effettuato un numero di passi uguali ad i, le medie vengono
quindi effettuate utilizzando la normalizzazione
for ( step = 0 ; step <= n_step ; ++step ) {
578
(Rev. 2.0.4)
if ( walks [ step ] != 0 ) {
norm = 1 . 0 / ( double ) ( walks [ step ] ) ;
...
}
}
che assicura in corretto conteggio dei walks ad ogni passo. Nel caso di condizioni al bordo
riflettenti o periodiche tutti gli elementi di walks saranno ovviamente uguali a n step cosicch`e
conteggio risulter`a corretto anche in questi casi.
Esercizi
1. Fissare L a 100 e graficare h(x x0 )2 i e h(y y0 )2 i in funzione del numero di passi per
walks fino a 50 passi. Determinare la pendenza delle curve e della curva h(x x0 )2 i +
h(y y0 )2 i. Confrontare il risultato con quello ottenuto nel caso unidimensionale.
2. Ripetere il calcolo con L pari a 10. Come cambiano le curve al variare delle condizioni
al bordo?
579
580
(Rev. 2.0.4)
7. Automi Cellulari
7.1. Automi Cellulari
(Rev. 2.0.3)
Gli automi cellulari sono una classe di modelli introdotti originariamente da von Neumann e
Ulam nel 1948 come una semplice idealizzazione dellevoluzione delle cellule a livello cellulare.
Un automa cellulare pu`o essere pensato come un insieme di cellette il cui stato pu`o assumere
un numero finito di stati discreti. Nellipotesi pi`
u semplice la celletta pu`o trovarsi in soli due
stati: 1 celletta viva o 0 celletta morta. Lo stato delle singole cellette cambia nel tempo
a seconda dello stato delle cellette vicine. Ad esempio se ci sono troppe cellette vicine vive,
la celletta muore.
Questi modelli sono un tipico esempio del processo di riduzione utilizzato in fisica nellanalisi
di sistemi complessi. Il punto di partenza `e che fenomeni complicati possano essere originati
da poche leggi semplici. Sotto questa ipotesi, si cercano quindi modelli semplici che basati
su queste leggi riproducano il comportamento dei sistemi pi`
u complicati. Al di l`a della loro
rilevanza fisica e/o biologica, questi modelli sono un esempio di un sistema dinamico discreto
che pu`o essere simulato esattamente su un computer. Pi`
u in dettaglio un automa cellulare `e
un sistema dinamico in cui:
Lo spazio `e discretizzato, ossia il sistema `e definito su un reticolo;
Le variabili di interesse sono definite solo sui siti del reticolo e possono assumere solo
una sequenza discreta di valori;
Il tempo `e discreto per cui levoluzione avviene ad intervalli regolari;
Lo stato delle variabili su ogni sito cambia ad ogni intervallo di tempo con una regola
locale che dipende solo dallo stato del sito e dei suoi vicini.
Levoluzione `e parallela, ossia il valore delle variabili viene cambiato simultaneamente
in tutti i siti.
A causa della sua origine biologica spesso i siti di un automa cellulare sono chiamati celle,
noi useremo indifferentemente il nome cella o sito. Gli automi cellulari trovano una vasta
applicazioni in molti campi della fisica che vanno dalla fluidodinamica alle galassie.
581
(Rev. 2.0.3)
destra. Le variabili definite su ogni sito possono assumere solo due valori (automa Booleano)
che per comodit`a, e senza perdere di generalit`a, vengono indicati con 0 e 1. Lautoma di
evolve nel tempo con una regola di evoluzione definita associando ad ogni configurazione di
un sito e dei suoi due primi vicini il nuovo stato del sito:
i1
i+1
rule
t+1
La regola di evoluzione pu`o essere facilmente espressa mediante una tavola che riporti nella
prima riga le possibili configurazioni dei tre siti al tempo t e nella seconda il corrispondente
lo stato del sito centrale al tempo successivo t + 1, come mostra il seguente esempio
t
t+1
111
0
110
1
101
0
100
1
011
1
010
0
001
1
000
0
I tre siti possono trovarsi in 8 configurazioni differenti per cui vi sono 28 = 256 regole diverse.
Le differenti regole sono usualmente indicate con un codice numerico ottenuto scrivendo nella
prima riga della tavola della regola i possibili stati dei tre siti ordinati da sinistra a destra in
modo che corrispondano alla rappresentazione binaria dei numeri 7, 6, 5, 4, 3, 2, 1, 0 e leggendo
la seconda riga come rappresentazione binaria di un numero decimale. Il numero cos` ottenuto
rappresenta il codice numerico della regola. Ad esempio nel caso della regola espressa dalla
tavola precedente si ha
01011010 0 27 + 1 26 + 0 25 + 1 24 + 1 23 + 0 22 + 0 21 + 0 20 = 90
per cui questa regola viene indicata come la regola 90. Dalla tavola si vede facilmente che
questa regola equivale a prendere la somma modulo 2 dei siti vicini per cui questa `e anche
nota come la regola modulo 2, o regola xor perch`e lo stato dello spin al tempo t + 1 `e 1
solo e lo stato di uno solo dei suoi primi vicini `e 1.
Il seguente programma simula un automa cellulare Booleano unidimensionale. Il programma
prende come input il codice numerico della regola, il numero di passi che si vuole effettuare
ed informazioni sulla configurazione iniziale. Questultima pu`o essere sia random che data .
Programma: cell aut-1d.c
/* ***************************************************************
- Descrizione :
Simulazione di un Automa Cellulare Booleano
unidimensionale con condizioni periodiche
582
(Rev. 2.0.3)
al bordo.
- Input :
Numeri di siti dell automa
Codice numerico regola
Passi di evoluzione temporale
Configurazione iniziale random :
seed e probabilita stato 1
Configurazione iniziale data
posizioni siti con stato 1
- Output :
Sul file OUT_F siti con stato 1 versus tempo
- Parametri :
OUT_F -> File output
- $Id: cell_aut -1d.c v 1.3 14.11.04 AC
**************************************************************** */
# include <s t d i o . h>
# include <math . h>
# include < s t d l i b . h>
/* Macros */
# define OUT F " cell_aut .dat"
/* sito automa cellulare */
typedef unsigned short int cell_t ;
/* automa cellulare */
typedef struct {
unsigned int size ;
cell_t
site ;
} cellular_t ;
/* Prototipi */
cell_t create_site
void
table_rule
void
bit_to_str
void
init_state
void
update_state
( int n ) ;
( cell_t rule , cell_t rule_code ) ;
( char str , int nchar , int n ) ;
( cellular_t aut , unsigned long seed ) ;
( cellular_t aut , cell_t rule ) ;
/*
/*
/*
/*
/*
/*
*/
*/
*/
*/
*/
*/
583
(Rev. 2.0.3)
unsigned long
seed ;
char
line [ 8 1 ] ;
FILE
fp ;
/* seme generatore n. r.
/* input buffer
/* puntatore file out
*/
*/
*/
/* Automa */
automata = ( cellular_t ) malloc ( sizeof ( cellular_t ) ) ;
if ( automata == NULL ) {
fprintf ( stderr , "Non enough space for automata \n" ) ;
return 1 ;
}
/* Parametri Automa */
printf ( " Numero siti automa
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%ud" , &automata>size ) ;
: ");
: ");
: ");
584
(Rev. 2.0.3)
585
(Rev. 2.0.3)
" , rule [ i ] ) ;
return ;
}
/* ---* bit_to_str ()
*
*/
void bit_to_str ( char str , int nchar , int n )
{
int
i;
unsigned int mask ;
mask = 1 << ( nchar 1 ) ;
for ( i = 0 ; i < nchar ; ++i ) {
str [ i ] = ( ( n & mask ) != 0 ) ? 1 : 0 ;
mask >>= 1 ;
}
str [ nchar ] = \0 ;
return ;
}
/* ---* init_state ()
*
*/
void init_state ( cellular_t aut , unsigned long int seed )
{
int
i;
double
r;
double p_one ;
char line [ 8 1 ] ;
if ( seed > 0 ) {
/* configurazione random */
printf ( " Configurazione random : %d siti\n" , aut>size ) ;
printf ( " Probabilita valore 1: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &p_one ) ;
srand ( seed ) ;
for ( i = 0 ; i < aut>size ; ++i ) {
r = ( double ) rand ( ) / ( RAND_MAX + 1 . 0 ) ;
aut>site [ i ] = ( r < p_one ) ? 1 : 0 ;
}
}
else {
586
(Rev. 2.0.3)
/* configurazione data */
for ( i = 0 ; i < aut>size ; ++i ) {
aut>site [ i ] = 0 ;
}
printf ( " Configurazione data: %d siti\n" , aut>size ) ;
printf ( " Specificare posizione siti 1\n" ) ;
while ( 1 ) {
printf ( " Posizione ( =< 0 fine ): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , &i ) ;
if ( i < 1 | | i > aut>size ) break ;
if ( ! aut>site [ i 1]) {
aut>site [ i 1] = 1 ;
}
else {
printf ( "Sito non valido , gia scelto \n" ) ;
}
}
}
return ;
}
/* ---* update_state ()
*
*/
void update_state ( cellular_t aut , cell_t rule )
{
int
i;
/* indice sito
int
im1 , ip1 ;
/* indice sito a sx e dx
int
conf_index ;
/* codice num conf 3 siti
cell_t new_site ;
/* nuova configurazione aut
*/
*/
*/
*/
*/
*/
*/
aut>site [ i ]
587
(Rev. 2.0.3)
free ( new_site ) ;
return ;
}
Il programma utilizza per lautoma cellulare loggetto automata di tipo cellular t che contiene la sia il numero di celle dellautoma, campo size, che il puntatore alla prima cella
dellautoma, campo site. Da momento che ciascuna cella pu`o assumere solo il valore 0 o 1 le
celle sono di tipo cell t definito come unsigned short int. Le celle dellautoma sono create
con la funzione create site()
automata>site = create_site ( automata>size ) ;
che alloca lo spazio di memoria per le automata->size celle dellautoma e ritorna il puntatore
alla prima cella.
Tavola: funzione table rule()
Il programma utilizza direttamente la tavola della regola che associa a ciascuna delle 8 configurazioni di tre siti consecutivi il nuovo valore del sito centrale. Questa scelta non `e necessariamente la pi`
u efficiente ma sicuramente `e la pi`
u flessibile. La tavola della regola `e
costruita dalla funzione table rule() che trasforma il codice numerico della regola nella
tavola corrispondente.
Interpretando lo stato di tre celle consecutive come la rappresentazione binaria di un numero
intero le 8 configurazioni possibili sono identificate univocamente dal valore di un indice i =
0, . . . , 7 cosicch`e la tavola della regola pu`o essere facilmente codificata con lausilio dellarray
di 8 elementi rule assegnando allelemento i-esimo di rule il nuovo valore del sito centrale
per la configurazione dei tre siti identificata dal valore dellindice i.
for ( i = 0 ; i <= 7 ; ++i ) {
rule [ i ] = rule_code & 1 ;
rule_code >>= 1 ;
}
Per trasformare il codice numerico della regola nella tavola si utilizza lespressione
rule_code & 1
il cui valore `e 0 o 1 a seconda che il primo bit a destra, quello meno significativo, di rule code
sia un bit 0 o un bit 1. Gli 8 bits della rappresentazione numerica della regola vengono letti
spostando successivamente a destra i bits di rule code,
rule_code >>= 1 ;
Dal momento che si leggono solo i primi 8 bits non ha importanza se lo shift a destra sia logico
od aritmetico. Tuttavia sebbene rule code sia un tipo intero senza segno se il codice della
regola viene dato in input con un segno negativo il risultato di queste operazioni dipende dalla
rappresentazione utilizzata dal computer per i numeri negativi. Ad esempio nella rappresentazione in complemento a due i primi otto bits di rule code conterranno la rappresentazione
588
(Rev. 2.0.3)
in complemento a due del codice della regola mentre nella rappresentazione in true magnitude
i primi otto bits conterranno la rappresentazione binaria del modulo del codice della regola.
Il fatto che rule code sia letto con la direttiva di conversione %hu
sscanf ( line , "%hu" , &rule_code ) ;
non vuol dire infatti che un valore negativo `e convertito nellequivalente valore positivo ma solo
che la rappresentazione binaria del valore negativo `e interpretata come la rappresentazione
binaria di un valore positivo. Di conseguenza se si vuole essere certi che per il codice della
regola vengano utilizzati solo valori positivi bisogna aggiungere un controllo esplicito nel
programma.
In alternativa agli operatori bit-a-bit si pu`o utilizzare
for ( i = 0 ; i <= 7 ; ++i ) {
rule [ i ] = rule_code % 2 ;
rule_code /= 2 ;
}
Anche in questo caso valgono ovviamente considerazioni analoghe alle precedenti se il codice
della regola viene dato in input con un segno negativo.
Per controllare che la tavola generata corrisponda alla regola voluta la funzione table rule()
ne stampa il contenuto utilizzando la funzione
void bit_to_str ( char str , int nchar , int n )
che converte i primi nchar bits della rappresentazione binaria del numero intero n nella stringa
puntata da str.
Configurazione Iniziale: funzione init state()
Il programma permette di scegliere tra una configurazione iniziale random o specificata nel
caso il valore del seme seed sia uguale a zero. Nel caso di configurazione random alle celle
viene assegnato il valore 1 con probabilit`a p one
for ( i = 0 ; i < aut>size ; ++i ) {
r = ( double ) rand ( ) / ( RAND_MAX + 1 . 0 ) ;
aut>site [ i ] = ( r < p_one ) ? 1 : 0 ;
}
589
(Rev. 2.0.3)
interrotto dallistruzione break quando viene inserita una posizione non valida. Nel ciclo
viene anche controllato che le posizioni non vengano inserite pi`
u volte
if ( ! aut>site [ i 1]) {
aut>site [ i 1] = 1 ;
}
else {
printf ( "Sito non valido , gia scelto \n" ) ;
}
Quando lo stato di tutte le cellette `e stato cambiato larray temporanea viene copiata su
quella che contiene la configurazione dellautoma
for ( i = 0 ; i < aut>size ; ++i ) {
aut>site [ i ] = new_site [ i ] ;
}
Luso di un array ausiliario non `e efficiente da un punto di vista dellutilizzo della memoria,
ed in effetti levoluzione sincrona dellautoma pu`o essere realizzata utilizzando solo due variabili temporanee ausiliarie. La scrittura dellalgoritmo diventa per`o pi`
u complessa e meno
trasparente, e viene lasciato come esercizio.
Il nuovo valore di ogni cella i viene determinato dalla la tavola della regola rule calcolando
il valore dellindice della configurazione dei tre siti adiacenti:
590
conf_index
=
+
(Rev. 2.0.3)
dove im1 e ip1 individuano il vicino si sinistra e di destra del sito i, ed assegnando al sito i
il valore ottenuto dalla tavola
new_site [ i ] = rule [ conf_index ] ;
Condizioni al bordo
Lautoma cellulare vive in uno spazio finito di L siti di conseguenza il primo ed lultimo sito
hanno un vicino in meno. Per ovviare a questo inconveniente si usano le Condizioni Periodiche
al Bordo associando come vicino di sinistra del primo sito lultimo sito, e come vicino di destra
dellultimo sito il primo sito. Geometricamente questo vuol dire che lautoma cellulare vive
su un anello:
.....
..
2
1
0
L1
L2
.
......
591
(Rev. 2.0.3)
(Rev. 2.0.3)
Gli automi cellulari bidimensionali seguono le stesse regole degli automi cellulari unidimensionali con la sola differenza che il numero delle celle vicine di cui bisogna tener conto per
determinare il nuovo stato di una cella `e pi`
u grande. Il numero di celle vicine dipende dal
reticolo su cui `e definito lautoma, ad esempio su un reticolo bidimensionale quadrato per
ogni cella vi sono 4 siti primi vicini, indicati con 0, 1, 2, 3 nella figura, e 4 siti secondi vicini
indicati con 4, 5, 6, 7 nella figura.
Di conseguenza se tutte queste otto celle concorrono a determinare lo stato della cella centrale,
sito 8 della figura, e se ciascuna cella pu`o trovarsi in soli due stati si ha un totale di 29 =
512 configurazioni diverse in cui possono trovarsi le 9 celle e quindi 2512 possibili regole di
evoluzione. Questa grande variet`a di regole unita al fatto che linterazione tra le varie celle
coinvolge gruppi pi`
u grandi di siti ha come conseguenza una fenomenologia di comportamento
pi`
u ricca.
Molto probabilmente il pi`
u noto automa cellulare booleano bidimensionale `e quello introdotto
da John Conway nel 1970 e chiamato Game of Life. Le regole del gioco sono molto semplici:
una cella viva rimarr`a viva se e solo se `e circondata da esattamente 2 o 3 celle vive. Se ve ne
sono pi`
u di 3 la cella morir`a per sovraffollamento, mentre se ve ne sono meno di 2 morir`a per
isolamento. Infine una cella morta diventa viva se `e circondata da esattamente 3 celle vive.
Se assegniamo a ciascun sito del reticolo il valore 1 se la cella `e viva e al valore 0 se la cella `e
morta, le regole del Game of Life diventano:
1
1
0
0
1
0
1
0
Levoluzione pu`o essere realizzata come nel caso unidimensionale utilizzando la tavola della
regola che associa a ciascuna delle 512 configurazioni dei nove siti il nuovo valore del sito
centrale.
Il Game of Life `e un esempio di una macchina calcolatrice universale (universal computing
machine) nel senso che la sua evoluzione pu`o essere vista come le fasi successive di processo di
calcolo. Infatti `e possibile mostrare che le differenti configurazioni delle celle corrispondono
ai diversi componenti di un computer incluse le connessioni la memoria e la CPU con le
operazioni logiche ed aritmetiche fondamentali.
Il seguente programma simula il Game of Life su un reticolo bidimensionale quadrato di
L L siti. Il programma prende come input il valore di L, il numero di passi temporali che
592
(Rev. 2.0.3)
si vuole effettuare ed informazioni sulla configurazione iniziale. Questultima pu`o essere sia
una configurazione random che una configurazione data. Loutput del programma `e sul file
life.dat definito dalla macros OUT F e contiene le coordinate delle celle vive in funzione
delliterazione temporale. Il programma usa condizioni periodiche al bordo.
Programma: game life.c
/* ***************************************************************
- Descrizione :
Simulazione dell Automa Cellulare Booleano
bidimensionale "Game of Life" con condizioni
periodiche al bordo.
Evoluzione con Tavola .
- Input :
Numeri di siti per lato dell automa
Passi di evoluzione temporale
Configurazione iniziale random :
seed e probabilita stato 1
Configurazione iniziale data
posizioni siti con stato 1
- Output :
Sul file OUT_F siti con stato 1 versus tempo
- Parametri :
OUT_F -> File output
- $Id: game_life .c v 1.3 14.11.04 AC
**************************************************************** */
# include <s t d i o . h>
# include <math . h>
# include < s t d l i b . h>
/* Macros */
# define OUT F "life.dat"
/* sito automa cellulare */
typedef unsigned short int cell_t ;
/* automa cellulare */
typedef struct {
unsigned int size ;
cell_t
site ;
} cellular_t ;
/* Prototipi */
cell_t create_site
( int n ) ;
593
void
void
void
int
(Rev. 2.0.3)
table_rule
init_state
update_state
ivect
( cell_t rule ) ;
( cellular_t aut , unsigned long seed ) ;
( cellular_t aut , cell_t rule ) ;
( int n ) ;
/*
/*
/*
/*
/*
/*
/*
/*
/* Automa */
automata = ( cellular_t ) malloc ( sizeof ( cellular_t ) ) ;
if ( automata == NULL ) {
fprintf ( stderr , "Non enough space for automata \n" ) ;
return 1 ;
}
/* Parametri Automa */
printf ( " Numero di siti per lato
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%ud" , &automata>size ) ;
printf ( " Passi temporali
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , &n_step ) ;
: ");
: ");
594
*/
*/
*/
*/
*/
*/
*/
*/
(Rev. 2.0.3)
/* Configurazione iniziale */
for ( i = 0 ; i < automata>size ; ++i ) {
for ( j = 0 ; j < automata>size ; ++j ) {
if ( automata>site [ i ] [ j ] ) fprintf ( fp , "%d %d %d\n" , 0 , i+1, j +1);
}
}
/* Evoluzione */
for ( step = 1 ; step <= n_step ; ++step ) {
update_state ( automata , rule ) ;
for ( i = 0 ; i < automata>size ; ++i ) {
for ( j = 0 ; j < automata>size ; ++j ) {
if ( automata>site [ i ] [ j ] ) {
fprintf ( fp , "%d %d %d\n" , step , i+1, j +1);
}
}
}
}
fclose ( fp ) ;
return 0 ;
}
/* ---* create_site ()
*
*/
cell_t create_site ( int n )
{
int
i;
cell_t c ;
c = ( cell_t ) malloc ( n sizeof ( cell_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( 1 ) ;
}
for ( i = 0 ; i < n ; ++i ) {
c [ i ] = ( cell_t ) malloc ( n sizeof ( cell_t ) ) ;
if ( c [ i ] == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( 1 ) ;
}
}
return c ;
}
/* ---* table_rule ()
595
(Rev. 2.0.3)
*
* Numerazione siti
*
5 1 4
*
2 8 0
*
6 3 7
*/
void table_rule ( cell_t rule )
{
int i1 , i2 , i3 ;
/* Siti vicini = 1,2,3,4,5,6,7 */
int
index ;
/* indice configurazione
*/
/* Tutti morti salvo regole sotto */
for ( index = 0 ; index < 5 1 2 ; ++index ) {
rule [ index ] = 0 ;
}
/* Tre vicini vivi */
for ( i1 = 0 ; i1 < 6 ; ++i1 ) {
for ( i2 = i1 + 1 ; i2 < 7 ; ++i2 ) {
for ( i3 = i2 + 1 ; i3 < 8 ; ++i3 ) {
index = ( 1 << i1 ) + ( 1 << i2 ) + ( 1 << i3 ) ;
rule [ index ]
= 1;
/* centro morto */
rule [ index +256] = 1 ;
/* centro vivo */
}
}
}
/* Due vicini vivi */
for ( i1 = 0 ; i1 < 7 ; ++i1 ) {
for ( i2 = i1 + 1 ; i2 < 8 ; ++i2 ) {
index = ( 1 << i1 ) + ( 1 << i2 ) ;
rule [ index +256] = 1 ;
}
}
/* centro vivo
return ;
}
/* ---* init_state ()
*
*/
void init_state ( cellular_t aut , unsigned long int seed )
{
int
i, j;
double
r;
double p_one ;
char line [ 8 1 ] ;
if ( seed > 0 ) {
/* configurazione random */
printf ( " Configurazione random : %d x %d siti\n" ,
596
*/
(Rev. 2.0.3)
aut>size , aut>size ) ;
printf ( " Probabilita valore 1: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &p_one ) ;
srand ( seed ) ;
for ( i = 0 ; i < aut>size ; ++i ) {
for ( j = 0 ; j < aut>size ; ++j ) {
r = ( double ) rand ( ) / ( RAND_MAX + 1 . 0 ) ;
aut>site [ i ] [ j ] = ( r < p_one ) ? 1 : 0 ;
}
}
}
else {
/* configurazione data */
for ( i = 0 ; i < aut>size ; ++i ) {
for ( j = 0 ; j < aut>size ; ++j ) {
aut>site [ i ] [ j ] = 0 ;
}
}
printf ( " Configurazione data: %d x %d siti\n" ,
aut>size , aut>size ) ;
printf ( " Specificare posizione siti 1\n" ) ;
while ( 1 ) {
printf ( " Posizione ( =< 0 fine ): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d%d" , &i , &j ) ;
if ( i < 1 | | i > aut>size ) break ;
if ( j < 1 | | j > aut>size ) break ;
if ( ! aut>site [ i 1 ] [ j 1]) {
aut>site [ i 1 ] [ j 1] = 1 ;
}
else {
printf ( "Sito non valido , gia scelto \n" ) ;
}
}
}
return ;
}
/* ---* update_state ()
*
*/
void update_state ( cellular_t aut , cell_t rule )
{
int
i, j;
/* Sito reticolo
int
im , ip , jm , jp ;
/* Siti vicini
int
index ;
/* Indice config
static int im1 , ip1 ;
/* Tavole vicini
*/
*/
*/
*/
597
(Rev. 2.0.3)
/* flag start
*/
/* nuova configurazione aut */
= ivect ( aut>size ) ;
= ivect ( aut>size ) ;
598
(Rev. 2.0.3)
/* ---* ivect ()
*/
int ivect ( int n )
{
int v ;
v = ( int ) malloc ( n sizeof ( int ) ) ;
if ( v == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory \n" ) ;
exit ( EXIT_FAILURE ) ;
}
return v ;
}
La struttura del programma `e uguale a quella del caso unidimensionale con la differenza che
in questo caso lautoma `e su un reticolo quadrato di dimensione L L per cui cellular t `e
definito come
typedef struct {
unsigned int size ;
cell_t
site ;
} cellular_t ;
con il campo site puntatore a puntatore di tipo cell t in modo da poter puntare allarray
bidimensionale che contiene il valore dei siti del reticolo.
Tavola: funzione table rule()
Come nel caso unidimensionale lalgoritmo viene realizzato mediante la tavola della regola
rule che per ogni configurazione dei 9 siti fornisce il nuovo valore del sito centrale. Le 512
configurazioni possibili dei nove siti sono identificate numerando i siti come
5
599
(Rev. 2.0.3)
La costruzione della tavola viene fatta in tre passi. Per prima cosa si assegna il valore 0 a
tutti i suoi 512 elementi
for ( index = 0 ; index < 5 1 2 ; ++index ) {
rule [ index ] = 0 ;
}
Poi si considerano separatamente i due casi in cui il nuovo valore del sito centrale `e 1. Il
primo caso `e quando vi sono esattamente 3 siti vicini con valore 1
for ( i1 = 0 ; i1 < 6 ; ++i1 ) {
for ( i2 = i1 + 1 ; i2 < 7 ; ++i2 ) {
for ( i3 = i2 + 1 ; i3 < 8 ; ++i3 ) {
index = ( 1 << i1 ) + ( 1 << i2 ) + ( 1 << i3 ) ;
rule [ index ]
= 1;
/* centro morto */
rule [ index +256] = 1 ;
/* centro vivo */
}
}
}
In questo caso il valore del sito centrale sar`a 1 sia che il sito centrale abbia valore 0
rule [ index ]
= 1;
/* centro morto */
che il valore 1
rule [ index +256] = 1 ;
/* centro vivo
*/
poich`e aggiungere 256 equivale ad assegnare valore di bit 1 al nono bit di index.
Laltro caso in cui il nuovo valore del sito centrale `e 1 `e quando il sito ha valore 1 ed `e
circondato da esattamente 2 siti con valore 1
for ( i1 = 0 ; i1 < 7 ; ++i1 ) {
for ( i2 = i1 + 1 ; i2 < 8 ; ++i2 ) {
index = ( 1 << i1 ) + ( 1 << i2 ) ;
rule [ index +256] = 1 ;
}
}
/* centro vivo
*/
600
(Rev. 2.0.3)
cell_t new_site ;
...
new_site = create_site ( aut>size ) ;
su cui viene memorizzato il nuovo valore dello stato dei siti generato a partire dai valori vecchi.
Quando lo stato di tutti i siti `e stato aggiornato si copia il contenuto dellarray temporanea
su quella che contiene la configurazione del sistema
for ( i = 0 ; i < aut>size ; ++i ) {
for ( j = 0 ; j < aut>size ; ++j ) {
aut>site [ i ] [ j ] = new_site [ i ] [ j ] ;
}
}
Se non si vuole allocare e deallocare larray temporaneo ad ogni chiamata della funzione si
pu`o definirlo una volta sola in classe static come fatto per le tavole dei vicini, vedi pi`
u sotto.
Questa scelta, che elimina le operazioni di allocazione/deallocazione della memoria, non `e
detto tuttavia che migliori automaticamente le prestazioni del programma poich`e lallocazione
fatta ad ogni chiamata permette al programma di utilizzare, se possibile, zone di memoria di
accesso pi`
u comodo migliorando cos` le prestazioni.
Evoluzione con Tavola della Regola
Il nuovo valore di ogni sito site[i][j] viene determinato con lausilio della tavola della
regola rule calcolando il valore dellindice index che identifica la configurazione degli otto
siti vicini pi`
u quella del sito stesso:
/* Primi vicini : 0, 1, 2, 3 */
index =
aut>site [ ip ] [ j ] + 2 aut>site [ i ] [ jp ] +
4 aut>site [ im ] [ j ] + 8 aut>site [ i ] [ jm ] ;
/* Secondi vicini : 4, 5, 6, 7 */
index += 16 aut>site [ ip ] [ jp ] + 32 aut>site [ im ] [ jp ] +
64 aut>site [ im ] [ jm ] + 128 aut>site [ ip ] [ jm ] ;
/* Sito centrale : 8 */
index += 256 aut>site [ i ] [ j ] ;
dove im, ip, jp e jm individuano le coordinate dei siti vicini, ed assegnando al sito site[i][j]
il nuovo valore dato dalla tavola
new_site [ i ] [ j ] = rule [ index ] ;
601
(Rev. 2.0.3)
la somma dei valori dei suoi quattro siti primi vicini 0, 1, 2, 3 e dei quattro siti secondi vicini
4, 5, 6, 7. Se il sito al centro, sito 8, ha il valore 1 rimarr`a 1 al tempo successivo se e solo se
la somma vale 2 o 3, in caso contrario il suo valore diventer`a 0. Se invece il sito al centro ha
il valore 0 diventer`a 1 al tempo successivo se e solo se la somma vale 3 e rimarr`a 0 in caso
contrario. La funzione update state() pu`o essere quindi riscritta come:
Funzione: update state.c
/* ---* update_state ()
*
* Game of Life
*
* Evoluzione con somma
*
* Numerazione siti
*
5 1 4
*
2 8 0
*
6 3 7
*/
void update_state ( cellular_t aut )
{
int
i, j;
/* Sito reticolo
int
im , ip , jm , jp ;
/* Siti vicini
int
sum ;
/* Somma vicini
static int im1 , ip1 ;
/* Tavole vicini
static int start = 1 ;
/* flag start
cell_t
new_site ;
/* nuova configurazione aut
/* Se start tavole vicini */
if ( start ) {
start
= 0;
im1
ip1
= ivect ( aut>size ) ;
= ivect ( aut>size ) ;
602
*/
*/
*/
*/
*/
*/
(Rev. 2.0.3)
im = im1 [ i ] ;
for ( j = 0 ; j < aut>size ; ++j ) {
jp = ip1 [ j ] ;
jm = im1 [ j ] ;
/* Primi vicini : 0, 1, 2, 3 */
sum =
aut>site [ ip ] [ j ] + aut>site [ i ] [ jp ] +
aut>site [ im ] [ j ] + aut>site [ i ] [ jm ] ;
/* Secondi vicini : 4, 5, 6, 7 */
sum += aut>site [ ip ] [ jp ] + aut>site [ im ] [ jp ] +
aut>site [ im ] [ jm ] + aut>site [ ip ] [ jm ] ;
if ( sum == 3 | | ( sum == 2 && aut>site [ i ] [ j ] ) ) {
new_site [ i ] [ j ] = 1 ;
}
else {
new_site [ i ] [ j ] = 0 ;
}
}
}
for ( i = 0 ; i < aut>size ; ++i ) {
for ( j = 0 ; j < aut>size ; ++j ) {
aut>site [ i ] [ j ] = new_site [ i ] [ j ] ;
}
}
free ( new_site ) ;
return ;
}
Il nuovo valore di ogni sito viene determinato dal suo valore e da quello della somma sum dei
valori dei suoi vicini
if ( sum == 3 | | ( sum == 2 && aut>site [ i ] [ j ] ) ) {
new_site [ i ] [ j ] = 1 ;
}
else {
new_site [ i ] [ j ] = 0 ;
}
Se la somma dei valori dei siti vicini `e uguale a 3 il nuovo valore del sito centrale sar`a uguale
a 1 indipendentemente dal suo valore. Se invece la somma `e uguale a 2 il nuovo valore sar`a
uguale a 1 se e solo se il suo valore `e gi`a uguale a 1. In tutti gli altri casi il nuovo valore del
sito centrale sar`a uguale a 0.
Condizioni al bordo
Lautoma cellulare `e definito su uno spazio finito di L L siti di conseguenza i siti al bordo
hanno uno o pi`
u vicini in meno dei siti allinterno del reticolo. Per ovviare a questo inconve-
603
(Rev. 2.0.3)
niente il programma utilizza le condizioni periodiche al bordo definendo come vicini di sinistra
dei siti della prima colonna i siti dellultima colonna e come vicini di destra dei siti dellultima
colonna i siti della prima colonna. Lo stesso viene fatto con i siti della prima ed ultima riga.
colonne vicine
L1
righe vicine
0
0
L1
Alternativamente gli arrays im1 e ip1 possono essere costruiti specificando direttamente i
valori dei suoi elementi, come `e stato fatto nello studio del Random Walk bidimensionale.
Utilizzando gli arrays ip1 e im1 le coordinate i1 e j1 sono date per ogni sito site[i][j]
da
ip
im
jp
jm
=
=
=
=
ip1 [ i ] ;
im1 [ i ] ;
ip1 [ j ] ;
im1 [ j ] ;
Per limitare il numero di operazioni da compiere gli arrays ip1 e im1 sono dichiarati in classe
di memorizzazione static e costruiti la prima volta che la funzione update state() viene
chiamata in modo da non doverle ricreare ogni volta. Questo `e ottenuto utilizzando la flag
start che vale 1 la prima volta che la funzione `e chiamata e 0 le successive.
static int start = 1 ;
...
if ( start ) {
start
= 0;
604
/* flag start
*/
(Rev. 2.0.3)
...
}
La variabile start deve essere dichiarata in classe di memorizzazione static perch`e deve
mantenere il suo valore da una chiamata allaltra della funzione ed inoltre deve essere inizializzata nella dichiarazione perch`e il valore 1 gli deve essere assegnato una sola volta.
# File : looper . gpl
#
plot $0 u ( $$1 == time ? $$2 : 1 / 0 ) : 3
pause 1
time = time + 1
if ( time < $1 ) reread
set xr [ 0 : 2 1 ]
set yr [ 0 : 2 1 ]
set nok
set size 0 . 7 5 , 1
time = 0
call looper .gpl "life.dat" 10
605
606
(Rev. 2.0.3)
8. Percolazione
8.1. Percolazione
(Rev. 2.0.4)
Il modo pi`
u semplice per introdurre la percolazione `e quello di considerare un reticolo bidimensionale, ad esempio quadrato, e di riempirne i siti a caso con un probabilit`a p. Questo
si ottiene facilmente estraendo per ogni sito un numero random r distribuito uniformemente
nellintervallo [0, 1] ed assegnandogli il valore 1 (pieno) se r < p o 0 (vuoto) nel caso contrario.
Due siti primi vicini sono detti collegati (linked) se hanno lo stesso valore. Linsieme di tutti
i siti connessi tra di loro tramite coppie di siti collegati definisce un cluster. Avremo quindi
`
clusters di siti pieni e clusters di siti vuoti che saranno gli uni i complementari degli altri. E
facile convincersi che a causa di questa complementarit`a quanto detto per gli uni varr`a con
semplici modifiche anche per gli altri per cui nel seguito ci concentreremo sui clusters formati
dai siti pieni
L
Cluster
Linked
1
1
` facile convincersi che se il valore di p `e piccolo allora i siti pieni formeranno clusters di pochi
E
siti, mentre per p vicino ad uno quasi tutti i siti saranno pieni e formeranno un grosso cluster
che indipendentemente dalle dimensioni lineari del reticolo si estender`a da un suo lato allaltro.
Questo cluster che permette di andare da un lato allaltro del reticolo passando per coppie di
siti vicini `e chiamato spanning cluster o cluster percolante. Questo modello di percolazione
in cui lo stato pieno o vuoto `e assegnato ai siti del reticolo `e chiamato site percolation.
Dal momento che per valori piccoli di p non esiste uno spanning cluster mentre esiste per
valori di p vicino ad uno `e chiaro che deve esistere un valore critico pc (L) per il quale lo
spanning cluster appare per la prima volta. Come evidenziato il valore di pc (L) in generale
dipende, oltre che dalle caratteristiche geometriche dalle dimensioni lineari L del reticolo,
tuttavia `e possibile dimostrare che nel limite L esiste un valore limite ben definito pc
tale che
Per p < pc
Per p pc
607
yaC-Primer: Percolazione
(Rev. 2.0.4)
608
yaC-Primer: Percolazione
(Rev. 2.0.4)
dei valori di p per cui lo spanning cluster appare. Unaltra `e quella di definire pc (L) come il
valore di p per cui la met`a delle configurazioni generate ha (almeno) uno spanning cluster.
Chiaramente in un sistema di dimensione lineare finita definizioni differenti daranno luogo in
generale a valori differenti di pc (L), tuttavia nel limite L tutti devono convergere allo
stesso valore limite pc .
Il seguente programma calcola per un valore della probabilit`a di occupazione p la probabilit`a
che in un reticolo quadrato LL vi sia almeno uno spanning cluster orizzontale. La probabilit`a
viene stimata come la frazione di configurazioni differenti del reticolo che presentano almeno
uno spanning cluster orizzontale definito come il cluster che parte da un sito di coordinate
(0, y) e raggiunge un sito di coordinate (L 1, y 0 ) con y e y 0 qualsiasi.
y
L1
Spanning Cluster
0
0
L1 x
A tal fine il programma genera differenti configurazioni del reticolo con siti pieni full e
vuoti empty, identificando per ciascuna configurazione i clusters di siti full che partono
dai siti di coordinate (1, y), y = 10, . . . , L 1. Le condizioni al bordo sono libere in entrambe
le direzioni per cui quando un cluster raggiunge un bordo qualsiasi si ferma.
Programma: sp cluster-rec.c
/* ***************************************************************
- Descrizione :
Probabilita spanning cluster orizzontale
in un reticolo quadrato L x L
Costruzione cluster ricorsiva
- Input :
Probabilita occupazione sito
Numero di configurazioni per statistica
- Output :
Sul stdout probabilita spanning cluster orizzontale
- Parametri :
L -> Dimensione lineare reticolo quadrato
609
yaC-Primer: Percolazione
(Rev. 2.0.4)
/*
/*
/*
/*
/*
/*
/*
/*
Coordinate reticolo
Reticolo L x L
# configurazioni con sp. c.
# configurazioni generate
Configurazione corrente
Probabilita occupazione
Seme Generatore Random
Input buffer
/* Dati input */
printf ( " Reticolo quadrato %d x %d\n\n" , L , L ) ;
printf ( " Probabilita occupazione : " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &prob ) ;
printf ( " Numero configurazioni
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , &n_sample ) ;
/* Inizializzazione generatore numeri random */
seed = 1 2 3 4 5 ;
srand ( seed ) ;
/* Costruzione reticolo */
sq = crea_sq ( L ) ;
n_touch = 0 ;
for ( sample = 0 ; sample < n_sample ; ++sample ) {
/* Generazione sample */
init_sq ( sq , prob ) ;
/* Spanning cluster orizzontale */
x = 0;
for ( y = 0 ; y < L ; ++y ) {
610
*/
*/
*/
*/
*/
*/
*/
*/
yaC-Primer: Percolazione
(Rev. 2.0.4)
611
yaC-Primer: Percolazione
(Rev. 2.0.4)
/* ---* percol ()
*
* Formulazione ricorsiva
* Ritorna 1 se trovato spanning cluster o 0 altrimenti
*/
int percol ( site_t sq , int x , int y )
{
static int xc = L 1 ;
static int yc = L 1 ;
if ( sq [ x ] [ y ] == full ) {
sq [ x ] [ y ] = seen ;
if ( x == xc ) return 1 ;
if ( x < xc && percol ( sq ,
if ( x > 1 && percol ( sq ,
if ( y < yc && percol ( sq ,
if ( y > 1 && percol ( sq ,
}
return 0 ;
x + 1, y
)
x 1, y
)
x
, y + 1)
x
, y 1)
)
)
)
)
return
return
return
return
1;
1;
1;
1;
}
Un modo semplice per associare un valore numerico agli stati possibili in cui si pu`o trovare
un sito del reticolo `e quello di utilizzare un tipo enumerativo,
typedef enum { empty , full , seen } site_t ;
Nel caso specifico ciascun sito pu`o trovarsi in stati differenti: vuoto, pieno, trovato. I primi
due stati si riferiscono allo stato iniziale del sistema mentre lultimo `e lo stato di un sito
identificato come appartenente ad un cluster. Lutilizzo del tipo enumerativo ci assicura che
i tre stati sono associati a valori numerici diversi.
Per costruire il reticolo quadrato si utilizza larray bidimensionale sq di dimensione L L il
cui elemento sq[i][j] contiene il valore associato allo stato del sito del reticolo di coordinate
(x, y) = (i, j). Il programma utilizza un array bidimensionale con allocazione dinamica della
memoria, di conseguenza sq `e dichiarato come puntatore a puntatore a tipo site t
site_t sq ;
612
yaC-Primer: Percolazione
(Rev. 2.0.4)
che alloca uno spazio di memoria per un array bidimensionale dinamico di nn oggetti di tipo
site t e ritorna il puntatore al primo elemento dellarray di puntatori al primo elemento dell
righe dellarray.
Le diverse configurazioni del reticolo sono generate dalla funzione
init_sq ( sq , prob ) ;
che assegna a ciascun sito del reticolo sq il valore full con probabilt`a prob
for ( x = 0 ; x < L ; ++x ) {
for ( y = 0 ; y < L ; ++y ) {
r = ( double ) rand ( ) / ( RAND_MAX + 1 . 0 ) ;
sq [ x ] [ y ] = ( r < prob ) ? full : empty ;
}
}
Infine per determinare se esiste almeno uno spanning cluster orizzontale si controlla se il
cluster che parte da uno qualsiasi dei siti pieni, ossia con valore full, sul lato sinistro del
reticolo, siti di coordinata x = 0, raggiunge il lato opposto ossia un sito qualsiasi si coordinata
x = L 1.
x = 0;
for ( y = 0 ; y < L ; ++y ) {
if ( ( sq [ x ] [ y ] == full ) && percol ( sq , x , y ) ) {
++n_touch ;
break ;
}
Il cluster `e identificato dalla funzione percol() che ritorna 1 se il cluster che parte dal sito
pieno sq[x][y] raggiunge il lato del reticolo di coordinata x = L 1 o 0 in caso contrario.
Noi siamo interessati a calcolare solo il numero di configurazioni con almeno uno spanning
cluster, di conseguenza se si trova uno spanning cluster si incrementa di uno il valore del
contatore n touch che conta il numero di configurazioni con almeno uno spanning cluster e
si interrompe il ciclo con listruzione break.
613
yaC-Primer: Percolazione
(Rev. 2.0.4)
Formulazione Ricorsiva
Lo schema logico dellalgoritmo `e simile a quello utilizzato per stampare il contenuto di un
albero binario:
Se il sito `e vuoto non si fa nulla, e la funzione ritorna il valore 0.
Se il sito `e pieno si segna cambiando il suo valore in seen in modo da non controllarlo
due volte:
if ( sq [ x ] [ y ] == full ) {
sq [ x ] [ y ] = seen ;
...
}
Leffettivo valore di seen non `e importante purch`e sia diverso da quello di full. Lutilizzo di un tipo enumerativo ci assicura che i valori di seen e full siano effettivamente
diversi, a meno di non averli dichiarati volutamente uguali!
Se il sito `e pieno e la sua coordinata x `e uguale a L 1 il sito `e sul bordo a destra per
cui si interrompe lidentificazione del cluster e la funzione ritorna il valore 1:
if ( x == xc ) return 1 ;
Osserviamo che in questo modo lidentificazione del cluster potrebbe non essere completa, ma questo non `e importante se siamo solo interessati a sapere se il cluster raggiunge il bordo destro o no.
Se il sito `e pieno e non si trova sul bordo a destra si controllano a turno i suoi quattro
siti primi vicini e se da quelli pieni parte un cluster che raggiunge il bordo destro:
if
if
if
if
(
(
(
(
x
x
y
y
<
>
<
>
xc
1
yc
1
&&
&&
&&
&&
percol ( sq ,
percol ( sq ,
percol ( sq ,
percol ( sq ,
x + 1, y
)
x 1, y
)
x
, y + 1)
x
, y 1)
)
)
)
)
return
return
return
return
1;
1;
1;
1;
614
yaC-Primer: Percolazione
(Rev. 2.0.4)
/* ---* percol ()
*
* Formulazione iterativa
* Ritorna 1 se trovato spanning cluster o 0 altrimenti
*/
int percol ( site_t sq , int x , int y )
{
static int xc = L 1 ;
stack_t
stack ;
stack_data pnt , pnt_r ;
/* inizializza lo stack */
stack = init_stack ( ) ;
/* Sito vuoto: restituisci 0 */
if ( sq [ x ] [ y ] == empty ) return 0 ;
/* Sito sul bordo: restituisci 1 */
if ( x == xc ) return 1 ;
/* Sito pieno non sul bordo: segnalo e ricorda sito */
sq [ x ] [ y ] = seen ;
pnt_r . x = x ;
pnt_r . y = y ;
push ( pnt_r , stack ) ;
/* Per ogni punto nello stack trova punti connessi
a dx , sx , dw e up
while ( filled ( stack ) ) {
/* punto in cima allo stack */
pnt_r = pop ( stack ) ;
*/
615
yaC-Primer: Percolazione
(Rev. 2.0.4)
}
/* Spostamento sx sui siti pieni:
sito vuoto
-> fine spostamento
sito sul bordo -> fine spostamento
Siti pieni sullo stack
*/
pnt . x = pnt_r . x 1 ;
pnt . y = pnt_r . y ;
while ( pnt . x > 0 && ( sq [ pnt . x ] [ pnt . y ] == full ) ) {
sq [ pnt . x ] [ pnt . y ] = 0 ;
push ( pnt , stack ) ;
pnt . x ;
}
/* Spostamento dw sui siti pieni:
sito vuoto
-> fine spostamento
sito sul bordo -> fine spostamento
Siti pieni sullo stack
*/
pnt . x = pnt_r . x ;
pnt . y = pnt_r . y 1 ;
while ( pnt . y > 0 && ( sq [ pnt . x ] [ pnt . y ] == full ) ) {
sq [ pnt . x ] [ pnt . y ] = 0 ;
push ( pnt , stack ) ;
pnt . y ;
}
/* Spostamento up sui siti pieni:
sito vuoto
-> fine spostamento
sito sul bordo -> fine spostamento
Siti pieni sullo stack
*/
pnt . x = pnt_r . x ;
pnt . y = pnt_r . y + 1 ;
while ( pnt . y < L && ( sq [ pnt . x ] [ pnt . y ] == full ) ) {
sq [ pnt . x ] [ pnt . y ] = 0 ;
push ( pnt , stack ) ;
++pnt . y ;
}
}
/* Stack vuoto -> ritorna 0 */
return 0 ;
}
La struttura logica di questa formulazione iterativa `e simile alla formulazione ricorsiva: partendo da un sito pieno si identificano tutti i siti pieni che si possono raggiungere dal sito
muovendosi rispettivamente sempre a destra, sempre a sinistra, sempre verso lalto e sempre
verso il basso. I siti pieni cos` identificati sono marcati cambiando il loro valore per non
contarli pi`
u volte e le loro coordinate sono memorizzate nello stack. Quando si raggiunge un
sito vuoto la ricerca riprende dal sito memorizzato in cima allo stack che contiene lultimo
` facile convincersi che in questo modo la ricerca di interrompe solo
sito pieno identificato. E
616
(Rev. 2.0)
quando lo stack `e vuoto, ossia quando tutto il cluster `e stato identificato. La ricerca si interrompe e lo stack svuotato anche nel caso in cui si trovi un sito pieno la cui coordinata x sia
uguale a L 1
if ( pnt . x == xc ) {
while ( filled ( stack ) ) pop ( stack ) ;
return 1 ;
}
Esercizi
Calcolare il valore di pc (L) per L = 10, 20, 40, 80, 160 stimato come il valore di p per
cui la probabilit`a di avere almeno uno spanning cluster diventa finita, ossia maggiore
dellinverso della radice quadrata del numero di configurazioni differenti generate per
ogni valore di p. Provare ad estrapolare i dati per L .
Ripetere il calcolo stimando il valore di pc come il valore di di p per cui la probabilit`a di
avere almeno un cluster `e 0.5. Confrontare i valori ottenuti in questo modo con quelli
dellesercizio precedente.
(Rev. 2.0)
Utilizzando il programma descritto per determinare la probabilit`a che per una data probabilit`
a
di occupazione p vi sia almeno uno spanning cluster orizzontale su un reticolo quadrato L L
`e facile stimare numericamente il valore della soglia di percolazione pc .
Dal momento che il valore di pc `e definito per un sistema di dimensione L la strategia
per determinare il valore di pc `e quella di stimare numericamente il valore di pc (L) per sistemi
di dimensione L crescente ed estrapolare poi i valori ottenuti per L riportandoli ad
esempio su un grafico in funzione di 1/L.
In generale il valore della soglia di percolazione pc (L) per un reticolo di dimensione lineare
finita L dipende oltre che dalle dimensioni del sistema anche dalla definizione utilizzata per
identificare la soglia di percolazione. Tuttavia nel limite L il valore di pc (L) deve
convergere per ogni definizione ragionevole verso lo stesso valore limite pc che definisce la
soglia di percolazione del sistema.
617
(Rev. 2.0)
Nel seguito utilizzeremo due definizioni differenti. Nella prima il valore di pc (L) `e definito
come il valore di p tale che per p > pc (L) la probabilit`a che vi sia almeno uno spanning cluster
orizzontale sia finita, ossia maggiore dellerrore statistico fatto nel calcolo della probabilit`
a
e stimato come linverso della radice quadrata del numero n sample di configurazioni del
reticolo utilizzate per il calcolo della probabilit`a che via almeno uno spanning cluster.
Nella seconda il valore di pc (L) `e definito come il valore di p tale che per p > pc (L) pi`
u della
met`a delle n sample configurazioni generate abbia almeno uno spanning cluster orizzontale.
I valori ottenuti sono riportati nella tavola seguente:
L
pc (L)(a)
pc (L)(b)
pc (L)(c)
10
20
30
40
50
60
80
160
320
0.465
0.509
0.540
0.548
0.552
0.555
0.566
0.576
0.582
0.370
0.458
0.494
0.511
0.526
0.533
0.544
0.565
0.576
0.593
0.592
0.592
0.592
0.592
0.592
0.592
0.592
0.592
La colonna (a) riporta i valori ottenuti con la prima definizione e n sample= 100, la colonna
(b) valori ottenuti con la prima definizione e n sample= 10000 ed infine la colonna (c) i
valori ottenuti con la seconda definizione e n sample= 10000. Come si vede a parit`a di L
e n sample la seconda definizione fornisce una stima migliore di pc , tuttavia estrapolando i
valori per L tutti e tre forniscono lo stesso valore di pc , entro lerrore statistico, come
mostra la figura seguente:
0.6
pc(L)
0.5
(a)
pc(L)
(b)
pc(L)
(c)
pc(L)
0.4
0
1/L
618
0.05
(Rev. 2.0)
Lerrore sui valori di pc (L) `e stato stimato come la met`a del valore di p di cui bisogna variare
la probabilit`a di occupazione di sito p perch`e la probabilit`a che vi sia almeno uno spanning
cluster orizzontale vari di una quantit`a pari allerrore statistico 1/ n sample. In ogni caso
lerrore `e confrontabile con le dimensioni dei simboli nella figura.
Estrapolando con un semplice fit lineare i valori per L , ossia per 1/L 0, si ottiene per
la percolazione di sito il valore pc = 0.590 0.001 con i dati ottenuti con la prima definizione e
il valore pc = 0.592 0.001 con i dati ottenuti con la seconda definizione, valori perfettamente
compatibili entro lerrore statistico.
619
620
(Rev. 2.0)
(Rev. 2.0.1)
Le immagini ad alta risoluzione richiedono grandi quantit`a di memoria per cui per molte
applicazioni pratiche `e utile avere buoni metodi di compressione. Lidea di base della compressione delle immagini `e abbastanza semplice, almeno nella sua formulazione, si tratta
infatti di trovare un algoritmo probabilistico che riproduca limmagine il meglio possibile.
Questo comporta una notevole riduzione della memoria in quanto bisogna solo memorizzare
i parametri dellalgoritmo e non tutta limmagine.
Le Iterated Function Systems (IFS) sono una classe di algoritmi di compressione di immagine
che permettono una notevole riduzione della memoria. Per introdurre le IFS consideriamo
una trasformazione affine da W : R2 R2 definita dalla matrice 2 2 A e dal vettore
bidimensionale b:
e
x
a b
+
W [x] := A x + b =
f
y
c d
Questa trasformazione pu`o essere vista come una combinazione di traslazioni, rotazioni e
dilatazioni. Una trasformazione affine `e detta contrattiva se esiste una costante di Lipschitz
s < 1 tale che
W [x] W [y] < s |x y|,
x, y.
Un
di N trasformazioni affini Wi e da un insieme di probabilit`a pi , con
PN IFS `e un insieme
`
e in contrattiva in media, ossia soddisfa la
i=1 pi = 1. E possibile mostrare che se la IFS `
condizione
sp11 sp22 sp33 spNN < N
dove si `e la costante di Lifschitz della i-esima trasformazione affine, allora la mappa
x(n + 1) = Wk(n) [x(n)]
dove k(n) sono numeri random indipendenti che assumono il valore i = 1, . . . , N con probabilit`a pi , ha un ben definito attrattore. Questo vuol dire che, salvo un insieme di misura
nulla, qualsiasi sia il punto iniziale x(0) i punti generati dalla mappa si troveranno dopo un
certo numero di iterazioni sempre sullo stesso oggetto geometrico, chiamato per lappunto
attrattore.
Unimmagine bidimensionale `e un oggetto geometrico bidimensionale per cui la sua compressione `e di fatto ricondotta al problema inverso di determinare lIFS che ha come attrattore
limmagine da comprimere. Per specificare un elemento di un IFS servono 6 parametri per
ogni trasformazione pi`
u 1 per la sua probabilit`a, di conseguenza un IFS composto di N trasformazioni richiede 7N 1 parametri, essendo al somma delle probabilit`a fissata. Di conseguenza
se N non `e troppo grande lIFS rappresenta un buon metodo di compressione.
621
(Rev. 2.0.1)
Il problema `e chiaramente quello di determinare lIFS una volta data limmagine. La possibilit`a di determinare lIFS `e assicurata da un teorema che asserisce che data unimmagine
esiste sempre un IFS tale che la distanza tra lattrattore e limmagine `e minore di una distanza
fissata a piacere. Il teorema tuttavia non fornisce un metodo per determinare lIFS. Lo studio
dei metodi per determinare lIFS spesso sono piuttosto complessi ed esulano dai nostri scopi,
per cui ci limiteremo a fornire solo alcuni esempi.
Esempio: Sierpinsky gasket
0.8
0.6
0.4
0.2
0.5
W
W1
W2
W3
a
1/2
1/2
1/2
b
0
0
0
c
0
0
0
d
1/2
1/2
1/2
1.5
e
0
1
1/2
f
0
0
1/2
p
1/3
1/3
1/3
d
0
0.22
0.24
0.85
e
0.16
0
0
0
f
0
1.6
0.44
1.6
W
W1
W2
W3
W4
a
0
0.20
0.15
0.85
622
b
0
0.26
0.28
0.04
c
0
0.23
0.26
0.04
p
0.01
0.07
0.07
0.85
a
1/3
1/3
1/3
1/3
1/3
1/3
1/3
1/3
b
0
0
0
0
0
0
0
0
c
0
0
0
0
0
0
0
0
d
1/3
1/3
1/3
1/3
1/3
1/3
1/3
1/3
e
0
0
0
1/3
1/3
2/3
2/3
2/3
f
0
1/3
2/3
0
2/3
0
1/3
2/3
(Rev. 2.0.1)
p
1/8
1/8
1/8
1/8
1/8
1/8
1/8
1/8
/* ***************************************************************
Numero di iterazioni
- Output :
- Parametri :
/* trasformazione W */
623
(Rev. 2.0.1)
624
(Rev. 2.0.1)
=
=
=
=
=
=
=
0.5;
0.0;
0.0;
0.5;
0.0;
0.0;
1./3.;
/* W_2 */
ifs [ 1 ] . a [ 0 ] [ 0 ]
ifs [ 1 ] . a [ 0 ] [ 1 ]
ifs [ 1 ] . a [ 1 ] [ 0 ]
ifs [ 1 ] . a [ 1 ] [ 1 ]
ifs [ 1 ] . b [ 0 ]
ifs [ 1 ] . b [ 1 ]
prb [ 1 ]
=
=
=
=
=
=
=
0.5;
0.0;
0.0;
0.5;
1.0;
0.0;
1./3.;
/* W_3 */
ifs [ 2 ] . a [ 0 ] [ 0 ]
ifs [ 2 ] . a [ 0 ] [ 1 ]
ifs [ 2 ] . a [ 1 ] [ 0 ]
ifs [ 2 ] . a [ 1 ] [ 1 ]
ifs [ 2 ] . b [ 0 ]
ifs [ 2 ] . b [ 1 ]
prb [ 2 ]
=
=
=
=
=
=
=
0.5;
0.0;
0.0;
0.5;
0.5;
0.5;
1./3.;
return ;
}
625
(Rev. 2.0.1)
Esercizi
Uno dei vantaggi delle IFS `e che lattrattore non `e troppo sensibile a piccoli errori nei
parametri, per cui non `e necessario determinare i parametri dellIFS in modo molto
accurato. Studiare come varia lattrattore delle IFS date modificando di poco i valori
dei parametri.
626
A. Appendici
A.1. Esempio: Moto di due pianeti con algoritmo di Runge-Kutta
(Rev. 2.0)
Questo programma calcola numericamente la traiettoria del moto di due pianeti utilizzando
lalgoritmo di Runge-Kutta del secondo o quarto ordine. Lo schema generale del programma
e le condizioni iniziali sono simili a quello che utilizza lalgoritmo di Verlet.
Programma: planets-rk.c
/* ***************************************************************
- Descrizione :
Calcola la traiettoria di due pianeti integrando
le equazioni del moto usando Runge -Kutta del
secondo o quarto ordine . Lintegrazione viene fatta
nel sistema del centro di massa.
Le condizioni iniziali mettono automaticamente il
centro di massa nell origine degli assi con
con velocita nulla.
Viene fatto un controllo sui dati iniziali per evitare
le collisioni .
Il programma controlla laccuratezza dell integrazione
dal energia meccanica .
- Input :
Massa pianeta 1
Massa pianeta 2
posizione iniziale pianeta 1
velocita iniziale pianeta 1
numero di iterazioni
- Output :
- Parametri :
passo di integrazione DT
File pianeta 1 OUT_P1
File pianeta 2 OUT_P2
Costante di gravitazione G
parametro di collisione (in read_init ) MIN_COS
627
(Rev. 2.0)
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
planet_ty create_planet ( int n ) ;
void
rk2 ( double dt , planet_ty p ) ;
void
rk4 ( double dt , planet_ty p ) ;
void
read_init ( planet_ty p , int t ) ;
void
write_out ( FILE pf , double t , planet_ty pln ) ;
double
energy ( planet_ty p ) ;
FILE
open_file ( const char path , const char mode ) ;
int main ( void )
{
int
tim ;
int
tim_max ;
double
dt = DT ;
planet_ty planet ;
FILE
outf_1 ;
FILE
outf_2 ;
planet = create_planet ( 2 ) ;
/* ***
* Leggi dati iniziali e dai
* alcune informazioni utili
*** */
628
/* # di passi di integrazione */
/* I due pianeti */
(Rev. 2.0)
============================================================== */
Routines di definizione
*/
============================================================== */
---create_planet ()
629
(Rev. 2.0)
return ( c ) ;
}
/* ============================================================== */
/*
Routines di I/O
*/
/* ============================================================== */
/* ---* read_init ()
*/
# define MIN COS 0 . 1
/* macro privata della funz. */
void read_init ( planet_ty p , int tm )
{
double mod_r , mod_v , c_angle ;
char
line [ 4 0 ] ;
printf ( "\nI due pianeti partono in modo che: \n" ) ;
printf ( "*) Centro di massa nell origine (0 ,0)\n" ) ;
printf ( "*) Velocita entro di massa nulla (0 ,0)\n" ) ;
/* Pianeta 1 */
printf ( "\ nMassa pianeta 1: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p [ 0 ] . mass ) ) ;
printf ( " posizione pianeta 1 (x,y) : " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p [ 0 ] . pos . x ) , &(p [ 0 ] . pos . y ) ) ;
printf ( " velocita pianeta 1 (vx ,vy): " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf%lf" , &(p [ 0 ] . vel . x ) , &(p [ 0 ] . vel . y ) ) ;
/* Pianeta 2 */
printf ( "\ nMassa pianeta 2: " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p [ 1 ] . mass ) ) ;
p [ 1 ] . pos . x = p [ 0 ] . mass p [ 0 ] . pos . x / p [ 1 ] . mass ;
p [ 1 ] . pos . y = p [ 0 ] . mass p [ 0 ] . pos . y / p [ 1 ] . mass ;
p [ 1 ] . vel . x = p [ 0 ] . mass p [ 0 ] . vel . x / p [ 1 ] . mass ;
p [ 1 ] . vel . y = p [ 0 ] . mass p [ 0 ] . vel . y / p [ 1 ] . mass ;
/* Controlla che i pianeti non collidano */
/* 1 - |cos (V_1 ,[ X_2 - X_1 ])| > MIN_COS */
mod_v = pow ( p [ 0 ] . vel . x p [ 0 ] . vel . x + p [ 0 ] . vel . y p [ 0 ] . vel . y , 0 . 5 ) ;
mod_r = pow ( p [ 0 ] . pos . x p [ 0 ] . pos . x + p [ 0 ] . pos . y p [ 0 ] . pos . y , 0 . 5 ) ;
c_angle = ( p [ 0 ] . pos . x p [ 0 ] . vel . x + p [ 0 ] . pos . y p [ 0 ] . vel . y ) /
( mod_v mod_r ) ;
630
(Rev. 2.0)
/* ---* write_out ()
*/
void write_out ( FILE pf , double time , planet_ty pln )
{
fprintf ( pf , "%f\t%f\t%f\t%f\t%f\n" , time , pln>pos . x , pln>pos . y ,
pln>vel . x , pln>vel . y ) ;
return ;
}
/* ---* open_file ()
*/
FILE open_file ( const char path , const char mode )
{
FILE fa ;
if ( ( fa = fopen ( path , mode ) ) == NULL )
{
fprintf ( stderr , "\n\ nErrore in apertura di %s\n" , path ) ;
exit ( EXIT_FAILURE ) ;
}
return fa ;
}
/* ============================================================== */
/*
Routines di Integrazione e Fisica
*/
/* ============================================================== */
/* ----
631
(Rev. 2.0)
* energy ()
*
* Calcola lenergia meccanica
*/
double energy ( planet_ty p )
{
double ene ;
double dist_x , dist_y , dist ;
dist_x = p [ 1 ] . pos . x p [ 0 ] . pos . x ;
dist_y = p [ 1 ] . pos . y p [ 0 ] . pos . y ;
dist
= pow ( dist_x dist_x + dist_y dist_y , 0 . 5 ) ;
ene
632
p [ 0 ] . vel . x ;
p [ 0 ] . vel . y ;
(Rev. 2.0)
= p [ 1 ] . vel . x ;
= p [ 1 ] . vel . y ;
= (( double ) G ) p [ 0 ] . mass d_x / den ;
= (( double ) G ) p [ 0 ] . mass d_y / den ;
return ( d ) ;
}
/* ---* rk2 ()
*
* Runge - Kutta 2nd order
*
* \dot x = f(t,x)
* k1 = f(t,x(t))
* k2 = f(t + dt/2, x(t) + (dt /2)* k1)
* x(t+1) = x(t) + dt * k2 + O(dt ^3)
*/
void rk2 ( double dt , planet_ty plan )
{
register int ip ;
double
dt2 ;
planet_ty
p_tmp ;
/* variabili temporanee */
deriv_ty
kappa ;
dt2 = 0 . 5 dt ;
p_tmp = create_planet ( 2 ) ;
/* k1 = f(t,x) */
kappa = compute_deriv ( plan ) ;
/* k2 = f(t+dt/2,x + dt*k1 /2) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . mass = plan [ ip ] . mass ;
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y
}
kappa = compute_deriv ( p_tmp ) ;
+
+
+
+
dt2
dt2
dt2
dt2
kappa [ ip ] . pos . x ;
kappa [ ip ] . pos . y ;
kappa [ ip ] . vel . x ;
kappa [ ip ] . vel . y ;
/* x(t+1) = x(t) + dt * k2 */
for ( ip = 0 ; ip < 2 ; ++ip ) {
plan [ ip ] . pos . x += dt kappa [ ip ] . pos . x ;
plan [ ip ] . pos . y += dt kappa [ ip ] . pos . y ;
plan [ ip ] . vel . x += dt kappa [ ip ] . vel . x ;
plan [ ip ] . vel . y += dt kappa [ ip ] . vel . y ;
}
633
(Rev. 2.0)
/* Variabili temporanee */
p_tmp = create_planet ( 2 ) ;
/* k0 = f(t,x) */
k [ 0 ] = compute_deriv ( plan ) ;
/* k1 = f(t + dt/2, x(t) + (dt /2)* k0) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . mass = plan [ ip ] . mass ;
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x + dt2
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y + dt2
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x + dt2
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y + dt2
}
k [ 1 ] = compute_deriv ( p_tmp ) ;
/* k2 = f(t + dt/2, x(t) + (dt /2)* k1) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x + dt2
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y + dt2
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x + dt2
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y + dt2
}
634
k [ 0 ] [ ip ] . pos . x ;
k [ 0 ] [ ip ] . pos . y ;
k [ 0 ] [ ip ] . vel . x ;
k [ 0 ] [ ip ] . vel . y ;
k [ 1 ] [ ip ] . pos . x ;
k [ 1 ] [ ip ] . pos . y ;
k [ 1 ] [ ip ] . vel . x ;
k [ 1 ] [ ip ] . vel . y ;
(Rev. 2.0)
k [ 2 ] = compute_deriv ( p_tmp ) ;
/* k3 = f(t + dt , x(t) + dt*k2) */
for ( ip = 0 ; ip < 2 ; ++ip ) {
p_tmp [ ip ] . pos . x = plan [ ip ] . pos . x
p_tmp [ ip ] . pos . y = plan [ ip ] . pos . y
p_tmp [ ip ] . vel . x = plan [ ip ] . vel . x
p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y
}
k [ 3 ] = compute_deriv ( p_tmp ) ;
+
+
+
+
dt
dt
dt
dt
k [ 2 ] [ ip ] . pos . x ;
k [ 2 ] [ ip ] . pos . y ;
k [ 2 ] [ ip ] . vel . x ;
k [ 2 ] [ ip ] . vel . y ;
create_planet ( int n ) ;
Osserviamo che, differentemente dal quanto fatto nel programma che usa lalgoritmo
di Verlet, la funzione create planet() funzione ritorna un puntatore ad un oggetto di
tipo planet ty.
planet_ty planet ;
/* I due pianeti */
635
(Rev. 2.0)
I due elementi planet[0] e planet[1] sono le strutture di tipo planet ty che contengono le informazioni sui due pianeti.
write_out ( outf_1 , 0 . 0 , &planet [ 0 ] ) ;
write_out ( outf_2 , 0 . 0 , &planet [ 1 ] ) ;
Questo `e il ciclo principale dellintegrazione. Ad ogni iterazione i pianeti vengono spostati per un tempo dt dalla funzione rk4() che utilizza lalgoritmo di Runge-Kutta
del quarto ordine. In alternativa si pu`o usare la funzione rk2() che invece utilizza
lalgoritmo di Runge-Kutta del secondo ordine. La due funzioni prendono gli stessi
parametri in modo da poterle scambiare facilmente e confrontare la precisione dei due
algoritmi.
c = ( planet_ty ) malloc ( n sizeof ( planet_ty ) ) ;
Riserva lo spazio per un array di n oggetti di tipo planet ty.
sscanf ( line , "%lf" , &(p [ 0 ] . mass ) ) ;
....
sscanf ( line , "%lf%lf" , &(p [ 0 ] . pos . x ) , &(p [ 0 ] . pos . y ) ) ;
La variabile pln `e un puntatore ad una struttura, per cui per accedere alla struttura
bisogna usare loperatore di dereferenziazione ->.
typedef struct {
dvect_ty pos ;
dvect_ty vel ;
} deriv_ty ;
636
(Rev. 2.0)
p [ 0 ] . vel . x ;
d [ 0 ] . pos . y = p [ 0 ] . vel . y ;
d [ 0 ] . vel . x = ( ( double ) G ) p [ 1 ] . mass d_x / den ;
d [ 0 ] . vel . y = ( ( double ) G ) p [ 1 ] . mass d_y / den ;
d [ 1 ] . pos . x
d [ 1 ] . pos . y
d [ 1 ] . vel . x
d [ 1 ] . vel . y
= p [ 1 ] . vel . x ;
= p [ 1 ] . vel . y ;
= (( double ) G ) p [ 0 ] . mass d_x / den ;
= (( double ) G ) p [ 0 ] . mass d_y / den ;
p_tmp ;
k [ 4 ] ;
La variabile k `e un array di 4 puntatori ad oggetti di tipo deriv ty. Gli elementi k[0],
k[1], k[2] e k[3] sono i quattro k che entrano nellalgoritmo di Runge-Kutta del quarto
ordine.
k [ 0 ] = compute_deriv ( plan ) ;
....
for ( ip = 0 ; ip <
p_tmp [ ip ] . mass
p_tmp [ ip ] . pos . x
p_tmp [ ip ] . pos . y
p_tmp [ ip ] . vel . x
2 ; ++ip ) {
= plan [ ip ] . mass ;
= plan [ ip ] . pos . x + dt2 k [ 0 ] [ ip ] . pos . x ;
= plan [ ip ] . pos . y + dt2 k [ 0 ] [ ip ] . pos . y ;
= plan [ ip ] . vel . x + dt2 k [ 0 ] [ ip ] . vel . x ;
637
(Rev. 2.0)
Esercizi
1. Controllare il valore dellenergia meccanica in funzione del passo di integrazione;
2. Sostituire allalgoritmo di integrazione del quarto ordine con quello del secondo ordine.
A parit`a di passo di integrazione, quale `e migliore?
3. Riscrivre il programma usando un array di puntatori ad oggetti di tipo planet ty come,
ad esempio, nel programma che usa lalgortimo di Verlet.
638