Sei sulla pagina 1di 261

yaC-Primer

Yet Another C-Primer

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

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

2.8.4. Operatore di assegnamento semplice . . . . . . . . . . . . . . . . . . .


2.8.5. Operatori di assegnamento composti . . . . . . . . . . . . . . . . . . .
2.8.6. Operatore , ed espressioni sequenziali . . . . . . . . . . . . . . . . .
2.8.7. Aritmetic Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9. Espressioni logiche e controllo di flusso (Rev. 2.1.1) . . . . . . . . . . . . . . . . .
2.9.1. Operatori di relazione e di uguaglianza . . . . . . . . . . . . . . . . . .
2.9.2. Operatori logici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9.3. Istruzione condizionale if . . . . . . . . . . . . . . . . . . . . . . . . .
2.9.4. Costruzione if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9.5. Espressioni condizionate . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9.6. Istruzione switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9.7. Istruzione goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10. Cicli (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10.1. Istruzione while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10.2. Istruzione for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10.3. Istruzione do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10.4. Istruzioni break e continue . . . . . . . . . . . . . . . . . . . . . . . .
2.11. Esempio: Rappresentazione binaria di un numero intero decimale di tipo unsigned (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.12. Esempio: Rappresentazione binaria di un numero intero decimale di tipo signed
(Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.13. Conversioni tra tipi e Cast (Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . . . .
2.13.1. Conversioni implicite . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.13.2. Divisione tra tipi interi e tra tipi floating-point . . . . . . . . . . . . .
2.13.3. Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.14. Input/Output (Rev. 2.1.6) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.14.1. Streams standard: stdin, stdout, stderr . . . . . . . . . . . . . . . . .
2.14.2. Dichiarazione, apertura e chiusura degli streams . . . . . . . . . . . .
2.14.3. Output su streams di testo: funzioni fprintf(), printf() e sprintf() . . .
2.14.4. Input da streams di testo: funzioni fscanf(), scanf() e sscanf() . . . . .
2.14.5. Input da stream di testo con buffer: funzione fgets() . . . . . . . . . .
2.14.6. Input/Output da streams binari . . . . . . . . . . . . . . . . . . . . .
2.15. Esempio: Interpolazione lineare di un set di dati (Rev. 2.1.1) . . . . . . . . . . .
2.16. Array (Rev. 2.1.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.16.1. Dichiarazione del tipo array . . . . . . . . . . . . . . . . . . . . . . . .
2.16.2. Inizializzazione di un array . . . . . . . . . . . . . . . . . . . . . . . .
2.16.3. Arrays multidimensionali . . . . . . . . . . . . . . . . . . . . . . . . .
2.17. Stringhe (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.17.1. Dichiarazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.17.2. Inizializzazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.17.3. Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.18. Il Preprocessore C (Rev. 2.1.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.18.1. Comandi e direttive . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.18.2. Comando #define . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.18.3. Comando #undef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.18.4. Comando #include . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

2.18.5. Comandi #if, #ifdef, #ifndef e #endif . . . . . . . . . . . . . . . . 171


2.18.6. Comandi #else e #elif . . . . . . . . . . . . . . . . . . . . . . . . . . 173
2.18.7. Operatore defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
2.19. Esempio: Istogramma di frequenza di un set di dati (Rev. 2.1.1) . . . . . . . . . 175
2.20. Funzioni (Rev. 2.1.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
2.20.1. Definizione del tipo funzione . . . . . . . . . . . . . . . . . . . . . . . 182
2.20.2. Dichiarazione del tipo funzione . . . . . . . . . . . . . . . . . . . . . . 186
2.20.3. Uso del tipo funzione: chiamata a funzione . . . . . . . . . . . . . . . 187
2.20.4. Passaggio per valore e passaggio per indirizzo . . . . . . . . . . . . . . 190
2.20.5. Funzioni senza parametri . . . . . . . . . . . . . . . . . . . . . . . . . 192
2.20.6. Programmazione Strutturata . . . . . . . . . . . . . . . . . . . . . . . 193
2.21. Funzioni ricorsive (Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
2.22. Scopo, Visibilit`a e Classe (Rev. 2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . 195
2.22.1. Scopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
2.22.2. Visibilit`a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
2.22.3. Classe di memorizzazione . . . . . . . . . . . . . . . . . . . . . . . . . 200
2.23. Esempio: Distanza e tempo di caduta di un sasso in presenza di attrito (Rev. 2.1.1) 208
2.24. Puntatori (Rev. 2.1.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
2.24.1. Operatori di referenza e dereferenza . . . . . . . . . . . . . . . . . . . 218
2.24.2. Operatori e puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
2.24.3. Dichiarazione di puntatori . . . . . . . . . . . . . . . . . . . . . . . . . 219
2.24.4. Puntatori a tipo array . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
2.24.5. Puntatori a funzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
2.24.6. Qualificatore const . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
2.24.7. Puntatore generico void * . . . . . . . . . . . . . . . . . . . . . . . . 228
2.24.8. Puntatore nullo NULL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
2.24.9. Operatore di assegnamento e conversione . . . . . . . . . . . . . . . . 229
2.24.10.Alcune considerazioni sui puntatori . . . . . . . . . . . . . . . . . . . . 231
2.25. Puntatori come parametri di funzione (Rev. 2.1.1) . . . . . . . . . . . . . . . . . 231
2.26. Puntatori ed arrays (Rev. 2.1.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
2.26.1. Puntatori, arrays e puntatori a tipo array . . . . . . . . . . . . . . . . 243
2.27. Arrays come parametri di funzione (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . 245
2.28. Arrays multidimensionali, puntatori e funzioni (Rev. 2.1) . . . . . . . . . . . . . 248
2.28.1. Organizzazione in memoria degli arrays multidimensionali . . . . . . . 248
2.28.2. Identificatori degli arrays multidimensionali e puntatori . . . . . . . . 251
2.28.3. Arrays multidimensionali e funzioni . . . . . . . . . . . . . . . . . . . 254
2.29. Funzioni come parametri di funzione (Rev. 2.1) . . . . . . . . . . . . . . . . . . . 256
2.30. Programmazione modulare (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . 259
2.31. Esempio: Modulo di integrazione numerica (Rev. 2.1.1) . . . . . . . . . . . . . . 265
2.32. Funzione main() (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
2.33. Arrays di puntatori e Puntatori a puntatori (Rev. 2.1) . . . . . . . . . . . . . . . 279
2.34. Dichiarazione di tipo: typedef (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . 283
2.35. Composizione di dichiaratori (Rev. 2.1) . . . . . . . . . . . . . . . . . . . . . . . 287
2.36. Funzioni con un numero variabile di parametri (Rev. 2.1) . . . . . . . . . . . . . 292
2.36.1. Utilities stdarg.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
2.37. Allocazione dinamica della memoria (Rev. 2.0.3) . . . . . . . . . . . . . . . . . . 297

2.37.1. Funzione malloc() . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


2.37.2. Funzione calloc() . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.37.3. Funzione realloc() . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.37.4. Funzione free() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.38. Esempio: Istogramma in frequenza con allocazione dinamica della memoria
(Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.39. Esempio: algoritmo mergesort per lordinamento di un array (Rev. 2.0.1) . . . .
2.40. Arrays multidimensionali dinamiche (Rev. 2.0) . . . . . . . . . . . . . . . . . . .
2.40.1. Creazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.40.2. Cancellazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.41. Tipo Struttura (Rev. 2.0.9) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.41.1. Dichiarazione del tipo struttura . . . . . . . . . . . . . . . . . . . . . .
2.41.2. Accesso ai campi di una struttura . . . . . . . . . . . . . . . . . . . .
2.41.3. Dichiarazione ed inizializzazione di una variabile di tipo struttura . . .
2.41.4. Campi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.41.5. Ordinamento e dimensione di una struttura . . . . . . . . . . . . . . .
2.41.6. Operazioni permesse e non . . . . . . . . . . . . . . . . . . . . . . . .
2.41.7. Bit Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.42. Tipo Unione (Rev. 2.0.3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.43. Puntatori, Funzioni, Strutture ed Unioni (Rev. 2.5.1) . . . . . . . . . . . . . . . .
2.43.1. Puntatori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.43.2. Funzioni e Strutture . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.44. Array di Strutture od Unioni (Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . .
2.45. Tipo Enumerativo (Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.46. Operatore sizeof (Rev. 2.0.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.47. Operatori bit-a-bit (Rev. 2.0.2) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.47.1. Rappresentazione dei dati binari . . . . . . . . . . . . . . . . . . . . .
2.47.2. Operatori logici bit-a-bit . . . . . . . . . . . . . . . . . . . . . . . . . .
2.47.3. Operatore di complemento bit-a-bit . . . . . . . . . . . . . . . . . . .
2.47.4. Operatori di shift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.48. Esempio: Semplice algoritmo di criptaggio (Rev. 2.0.2) . . . . . . . . . . . . . . .
2.49. Esempio: Stampa bit-a-bit e Modulo bit utils.c (Rev. 2.0.3) . . . . . . . . . .
2.50. Ordinamento dei bytes nei tipi multi-byte (Rev. 2.0.2) . . . . . . . . . . . . . . .

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

5.5. Metodo di Metropolis


6. Random Walk
6.1. Random Walk
6.2. Random Walk
6.3. Random Walk
6.4. Random Walk

(Rev. 2.0.2)

. . . . . . . . . . . . . . . . . . . . . . . . . .

in una dimensione: Caso Discreto (Rev. 2.0.4) . . . . . . . . . .


Discreto Simmetrico in una Dimensione (Rev. 2.0.5) . . . . . . .
Discreto Simmetrico in una Dimensione per N  1 (Rev. 2.0.4)
Discreto in due Dimensioni in un dominio finito (Rev. 2.0.4) . .

.
.
.
.

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

yaC-Primer: Metodo della Bisezione

(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

/* Numero massimo di bisezioni */

double bisect ( double ( fun ) ( double ) , double x1 , double x2 ,


double xacc )
{
int
bis ;
/* contatore bisezioni
*/

394

yaC-Primer: Metodo della Bisezione

double x_mid ;
double y1 , y2 ;
double y_mid ;
double
dx ;

/*
/*
/*
/*

punto centrale intervallo


funzione estremi intervallo
funzione centro intervallo
lunghezza intervallo

(Rev. 2.1)

*/
*/
*/
*/

/* valore della funzione agli estremi dell intervallo */


y1 = ( fun ) ( x1 ) ;
y2 = ( fun ) ( x2 ) ;
/* esiste soluzione */
if ( ( y1 y2 ) > 0 . 0 )
return strtod ( "NAN" , NULL ) ;
/* lunghezza iniziale intervallo */
dx = x2 x1 ;
/* ciclo sulle bisezioni */
for ( bis = 0 ; bis < BIS_MAX ; ++bis ) {
dx
= 0 . 5 ;
x_mid = x1 + dx ;
y_mid = ( fun ) ( x_mid ) ;
if ( ( y1 y_mid ) > 0 . 0 ) {
x1 = x_mid ;
y1 = y_mid ;
}
if ( dx < xacc | | y_mid == 0 . 0 ) return x_mid ;
}
return strtod ( "NAN" , NULL ) ;
}
# undef BIS MAX


Note sul file bisect1.c


y1 = ( fun ) ( x1 ) ;
y2 = ( fun ) ( x2 ) ;

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

yaC-Primer: Metodo della Bisezione

(Rev. 2.1)

strtod ( "NAN" , NULL )

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

yaC-Primer: Metodo della Bisezione

(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

/* Numero massimo di bisezioni */

double bisect ( double fun ( double ) , double x1 , double x2 ,


double xacc )
{
int
bis ;
/* contatore bisezioni
*/
double
y1 , y2 ;
/* funzione estremi intervallo
*/
double
x_ref ;
/* estremo riferimento
*/
double x_mid , y_mid ;
/* punto centrale e funzione nel punto */
double
dx ;
/* lunghezza intervallo
*/
/* valore della funzione agli estremi dell intervallo */
y1 = fun ( x1 ) ;
y2 = fun ( x2 ) ;
/* esiste soluzione */
if ( ( y1 y2 ) > 0 . 0 )
return strtod ( "NAN" , NULL ) ;
/* funzione negativa all estremo di riferimento */
if ( y1 < 0 ) {
x_ref = x1 ;
dx
= x2 x1 ;
/* dx > 0 */

397

yaC-Primer: Metodo della Bisezione

}
else {
x_ref = x2 ;
dx
= x1 x2 ;
}

(Rev. 2.1)

/* dx < 0 */

/* ciclo sulle bisezioni */


for ( bis = 0 ; bis < BIS_MAX ; ++bis ) {
dx
= 0 . 5 ;
x_mid = x_ref + dx ;
y_mid = fun ( x_mid ) ;
if ( y_mid < 0 . 0 ) x_ref = x_mid ;
if ( fabs ( dx ) < xacc | | y_mid == 0 . 0 ) return x_mid ;
}
return strtod ( "NAN" , NULL ) ;
}
# undef BIS MAX


Note sul file bisect2.c


if ( y1 < 0 ) {
x_ref = x1 ;
dx
= x2 x1 ;
}
else {
x_ref = x2 ;
dx
= x1 x2 ;
}

/* 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 ) ;

che restituisce il valore assoluto del suo argomento.

398

yaC-Primer: Metodo della Bisezione

(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 ,

int main ( void )


{
double root ;
root = bisect ( f , 1.0 , 1 . 0 , . 0 0 0 1 ) ;
printf ( "\n" ) ;
printf ( " radice : %f\n" , root ) ;
printf ( " funzione : %f\n\n" , f ( root ) ) ;
return 0 ;
}
double f ( double x )
{
return ( x + 0 . 1 ) ;
}


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

Quando il programma viene eseguito si ha come output


radice : 0.100037
funzione : 0.000037

Se per qualche motivo la funzione non pu`o determinare una stima numerica della soluzione
si ha come output
radice : nan
funzione : nan

399

yaC-Primer: Metodo della Bisezione

(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 ) ;

il cui valore `e nonzero se il valore di x `e NaN cosicch`e il test


if ( isnan ( x ) )

`e vero se il valore di x `e NaN.


Il programma per controllare la funzione bisect() pu`o quindi essere riscritto come

400

yaC-Primer: Bracketing

(Rev. 2.1)

Programma: test-bisect nan.c (C99)


/*
* 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 ,

int main ( void )


{
double root ;
root = bisect ( f , 1.0 , 1 . 0 , . 0 0 0 1 ) ;
if ( isnan ( root ) == 0 ) {
printf ( "\n" ) ;
printf ( " radice : %f\n" , root ) ;
printf ( " funzione : %f\n\n" , f ( root ) ) ;
}
else {
printf ( " Radice non trovata \n\n" ) ;
}
return 0 ;
}
double f ( double x )
{
return ( x + 0 . 1 ) ;
}


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.

2.2. Bracketing delle radici

(Rev. 2.1)

Il metodo della bisezione per determinare una soluzione reale dellequazione


f (x) = 0
dove f (x) `e una funzione continua a valori reali, richiede che lintervallo iniziale fornito per
iniziare la ricerca contenga una ed una sola radice dellequazione. Di conseguenza per poter
utilizzare questo metodo `e necessario, a meno che non sia noto per altre vie, individuare

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

/* Fattore scala intervallo */


/* Numero massimo espansioni */

int root_brack1 ( double ( func ) ( double ) , double a , double b )


{
int
i;
double f_a , f_b ;
if ( a == b ) return 1 ;

/* intervallo iniziale invalido */

/* valore funzione agli estremi intervallo */


f_a = func ( a ) ;

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 */

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 ) ;
}
}
return 1 ;
}
# undef SCALE
# undef N TRY


/* 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;

e termina quando `e stato trovato il numero di sottointervalli richiesto


if ( nb == nbb ) return ;

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>

static double f ( double ) ;


extern double bisect ( double ( func ) ( double ) , double a , double b ,

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 [ ] ) ;

int main ( void )


{
int nb , i ;
double root , acc ;
double a , b ;
double x_1 [ 1 0 ] , x_2 [ 1 0 ] ;
a
= 1.0;
b
=
0.0;
acc = 0 . 0 0 0 1 ;
printf ( "\n" ) ;
printf ( "1) intervallo iniziale :
if ( root_brack1 ( f , &a , &b ) == 0 )
printf ( "1) intervallo trovato
root = bisect ( f , a , b , acc ) ;
printf ( "1) radice
printf ( "1) funzione
}

[%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)

Siccome il programma usa anche la funzione bisect() il programma va compilato con il


comando
$cc testbrack . c root_brack1 . c root_brack2 . c bisect2 . c

Quando il programma viene eseguito produce il seguente output


1)
1)
1)
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.

3.1.1. Metodo dei Rettangoli


Il metodo dei rettangoli si basa sullapprossimazione del valore dellintegrale su un intervallo
di ampiezza h con larea di un rettangolo di base h ed altezza data dal valore di f (x) nel
punto medio dellintervallo.

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

yaC-Primer: Calcolo Integrali

(Rev. 2.1.1)

Se quindi lintervallo [a, b] viene diviso in N intervalli di ampiezza h = (a b)/N si ha


Z

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 ).

3.1.2. Metodo dei Trapezi


Il metodo dei trapezi approssima il valore dellintegrale su un intervallo di ampiezza h con
larea di un trapezio di base h ed altezze date dal valore di f (x) agli estremi dellintervallo.

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

f (x) dx ' f (x0 ) (x1 x0 )


x0

i (x x )
1
0
f (x1 ) f (x0 )
2
h
i (x x )
1
0
= f (x0 ) + f (x1 )
2
+

Se quindi lintervallo [a, b] viene diviso in N intervalli di ampiezza h = (a b)/N si ha


Z
a

N
1
X
h
h
f (x) dx ' f (x0 ) +
h f (xi ) + f (xN )
2
2
i=1

con x0 = a, xN = b poiche il contributo degli intervalli adiacenti si somma.


Il metodo dei trapezi `e esatto se la funzione `e o una costante o lineare in x, ne segue che
lerrore `e O(h3 ). Di nuovo `e facile verificare lerrore usando successivamente le funzioni di
prova: f (x) = 1, f (x) = x e f (x) = x2 .

3.1.3. Metodo di Simpson


Prima di ricavare la formula di Simpson mostriamo un metodo alternativo, ma facilmente
generalizzabile ad algoritmi di ordine superiore, di ottenere formula dei trapezi. Supponiamo
di voler scrivere una formula di integrazione sullintervallo [x, x + h] che utilizzi solo i valori

410

yaC-Primer: Calcolo Integrali

(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

poiche luguaglianza deve essere vera per ogni valore di h.


Osserviamo che la prima condizione su a e b `e uguale a quella ottenuta con la funzione
` possibile mostrare che questa non `e una coincidenza fortuita.
f (x) = 1. E
Le due condizioni forniscono i valori a = b = 1/2, cos` lespressione (3.1) diventa:
Z

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

yaC-Primer: Calcolo Integrali

(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

Collezionando insieme tutte le condizioni si ottiene il

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

yaC-Primer: Calcolo Integrali

(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.

3.1.4. Metodo di Adams-Bashford


A volte pu`o accadere che ad uno dei due estremi dellintegrale la funzione non possa essere
calcolata cosicche le formule date non sono applicabili. In questi casi per calcolare lintegrale
si possono utilizzare i valori della f (x) in altri punti. Di seguito riportiamo due formule per
lintegrazione su un intervallo [x, x + h] che usano entrambi informazioni fuori dellintervallo
di integrazione. La prima usa i valori della funzione in x ed in x h:
Z x+h
i
hh
f (x) dx '
3 f (x) f (x h)
2
x
mentre la seconda usa i valori della funzione in x + h ed in x + 2h:
Z x+h
i
hh
3 f (x + h) f (x + 2h)
f (x) dx '
2
x
Per entrambe le formule lerrore `e O(h3 ). Queste formule possono essere ottenute seguendo lo
stesso procedimento utilizzato per la formula di Simpson, e la loro derivazione viene lasciata
per esercizio.
Schemi di integrazione che utilizzano il valore della funzione integranda ad uno solo dei due
estremi vengono chiamati schemi di integrazione semiaperti.

3.1.5. Metodo Monte Carlo


Il metodo Monte Carlo si basa su una scelta casuale di n punti dove valutare la funzione
integranda f (x). Il principale vantaggio del metodo `e la sua facile realizzazione ed estensione
anche ad integrali in pi`
u di una variabile di integrazione. Il metodo risulta inoltre particolarmente utile quando le funzioni da integrare sono particolarmente complesse e permette di
limitare gli errori dovuti ad una cattiva scelta dei punti di divisione dellintervallo [a, b].
Il principale svantaggio del metodo `e che il numero di punti n su cui calcolare la funzione
f (x) deve essere molto grande. Il metodo Monte Carlo `e infatti un metodo statistico e lerrore

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

yaC-Primer: Errore integrazione Monte Carlo

(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

ossia proprio il valore dellintegrale da calcolare.


Una buona stima del valore di aspettazione hsi `e fornita dalla media di s(x) sugli n valori xi
estratti, per cui
Z b
n
1 X
f (x) dx '
si
n
a
i=1

Come distribuzione di probabilit`a si pu`o prendere ad esempio una distribuzione uniforme


nellintervallo [a, b]:
(
1
se x [a, b]
ba
P (x) =
0
se x 6 [a, b]
in modo che tutti i punti nellintervallo siano considerati alla stessa stregua. Sostituendo
questa scelta nella definizione della variabile ausiliaria s(x) si ottiene la semplice relazione
Z
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.

3.2. Errore dellintegrazione con il metodo Monte Carlo

(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

yaC-Primer: Errore integrazione Monte Carlo

(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 ;

/* # sequenze di n_pt punti generate */


/* # punti per sequenza , e numero max */

n_sam = 1 0 0 ;
max_pt = 1 0 0 0 0 ;
fp = fopen ( " int_mc .dat" , "w" ) ;

415

yaC-Primer: Errore integrazione Monte Carlo

(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 programma `e composto da due cicli for uno dentro laltro.

for ( n_pt = 1 0 ; n_pt < max_pt ; n_pt = 5 ) {


mean_1 = mean_2 = 0 . 0 ;
for ( sam = 0 ; sam < n_sam ; ++sam ) {
value = integ_mc ( 0 , 1 . 0 , n_pt , 0 , f ) ;
...
}
...
}

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

yaC-Primer: Algoritmo di Eulero


n

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

yaC-Primer: Algoritmo di Eulero

(Rev. 2.1)

3.3. Equazioni Differenziali Ordinarie: Algoritmo di Eulero

(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)

dove r indica genericamente una posizione e t il tempo. La necessit`a di dover integrare


numericamente delle equazioni differenziali non `e tuttavia ristretto alla meccanica per cui
il problema verr`a trattato da un punto di vista generale senza far riferimento ad equazioni
differenziali particolari ottenute dallo studio di problemi di fisica o di altri campi.
La prima osservazione nella ricerca di un algoritmo per la soluzione numerica delle equazioni
differenziali `e che la soluzione di unequazione differenziale ordinaria di ordine qualsiasi pu`
o
sempre essere ridotta a quella di un sistema di equazioni differenziali ordinarie del primo
ordine. Ad esempio la soluzione dellequazione (3.2) `e data dalla soluzione del sistema di
equazioni differenziali del primo ordine:

dr

dt = v(t)

dv
dt

= b(t, r) a(t, r) v(t)

dove v(t) `e una nuova variabile, chiamata velocit`a in meccanica.


Di conseguenza senza perdere di generalit`a `e possibile limitarsi alla ricerca di un algoritmo per
la soluzione numerica di un sistema di N equazioni differenziali ordinarie del primo ordine:

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),

dv f 2 (x) = b(x, y 1 ) a(x, y 1 ) y 2 (x)


dt

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

yaC-Primer: Algoritmo di Eulero

(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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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

Utilizzando infine il fatto che in assenza di errore si deve avere


yn+1 = yn + h f (xn , yn )
e considerando solo il termine lineare, si ottiene lequazione di evoluzione dellerrore



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.

3.4. Esempio: Integrazione dellequazione del moto del pendolo


con lalgoritmo di Eulero (Rev. 2.1.1)
Come esempio di integrazione di un equazione differenziale ordinaria con lalgoritmo di integrazione di Eulero considereremo lintegrazione dellequazione del moto di un pendolo semplice
x
= 2 sin(x)

420

yaC-Primer: Integrazione equazione del pendolo con Eulero

(Rev. 2.1.1)

con una condizione iniziale data:


x(0) = x0 ,

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

yaC-Primer: Integrazione equazione del pendolo con Eulero

int main ( void )


{
int
stp ;
int
stp_max ;
double x_stp , v_stp ;
double
dt ;
char file_output [ 4 0 ] ;

/*
/*
/*
/*
/*

(Rev. 2.1.1)

contatore passi integrazione


# massimo passi integrazione
posizione e velocit a pendolo
passo integrazione
nome file di output

*/
*/
*/
*/
*/

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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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 ) ;

Per poter identificare facilmente i files con le traiettorie corrispondenti a condizioni


iniziali differenti il programma scrive la traiettoria su un file di nome
angolox_X . XXv_Y . YY . dat

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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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 ) ;

Lo schema di integrazione va applicato in parallelo su tutte le variabili: il nuovo valore


delle variabili `e determinato a partire dal vecchio valore di tutte variabili. Ad esempio
lo schema seguente
x = x + h x_dot ( x , v ) ;
v = v + h v_dot ( x , v ) ;

/* 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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(Rev. 2.1.1)

Luso di due variabili rende tuttavia il programma pi`


u chiaro.

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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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 ;
}


Funzione read init()


La funzione
void read_init ( double x , double v , double h , int sm ) ;

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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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

yaC-Primer: Integrazione equazione del pendolo con Eulero

(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.

Compilazione, esecuzione e precisione


Il programma viene compilato ad esempio come
$ cc pendulumeu . c pendulumio . c lm

Quando il programma viene eseguito si ha


Angolo Iniziale ( radianti ) : . 1
Velocita Iniziale
: 0
dt
: .01
# iterazioni
: 1000
File Output : angolo -x_0 .10 - v_0 .00. dat

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

yaC-Primer: Algoritmo di Verlet

(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 .

3.5. Equazioni Differenziali Ordinarie del secondo ordine:


Algoritmo di Verlet (Rev. 2.1)
Nello studio della meccanica spesso si incontrano problemi che richiedono la soluzione di
equazioni differenziali ordinarie del secondo ordine della forma:
d2 i

y (x) = f i (y 1 , . . . , y N ) =
V (y 1 , . . . , y N ),
dx2
yi

i = 1, . . . , N

(3.3)

con la condizione condizione iniziale:


i

y (x0 ) =

y0i ,


d i
y (x)
= v0i .
dx
x=x0

(3.4)

429

yaC-Primer: Algoritmo di Verlet

(Rev. 2.1)

la funzione V (y 1 , . . . , y N ) `e chiamata lenergia potenziale o semplicemente potenziale.


Queste equazioni possono essere trasformate facilmente in un sistema di equazioni differenziali
del primo ordine ed integrate numericamente ad esempio con lalgoritmo di Eulero. Tuttavia
questa non rappresenta la scelta migliore. Il motivo `e che queste equazioni hanno la propriet`
a
di conservare il valore della grandezza
N

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, . . .

e yni y i (xn ) il valore della variabile y i per x = xn .


Uno dei vantaggi dellalgoritmo di Verlet rispetto ad altri algoritmi che utilizzano un solo
valore delle funzioni f i (y 1 , . . . , y N ) `e leccellente conservazione dellenergia meccanica lungo
la traiettoria (xn , yn1 , . . . , ynN ).
La formula dellalgoritmo di Verlet si pu`o ottenere facilmente considerando lo sviluppo di
Taylor di y i (x + h) e y i (x h) fino al terzo ordine in h:
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

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

Sommando queste due espressioni si ha:


y i (x + h) + y i (x h) = 2 y i (x) + h2

430

d2 i
y (x) + O(h4 )
dx2

yaC-Primer: Integrazione equazione del pendolo con Verlet

(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.

3.6. Esempio: Integrazione dellequazione del moto del pendolo


con lalgoritmo di Verlet (Rev. 2.1.1)
Lalgoritmo di integrazione di Eulero usato nellesempio precedente per integrare lequazione del moto del pendono semplice non `e molto preciso ed infatti lenergia non si conserva
molto bene, a meno di non usare un passo di integrazione molto piccolo. A parit`a di passo
di integrazione lalgoritmo di Verlet fornisce risultati nettamente migliori. Per confrontare i
due algoritmi il seguente programma integra nuovamente lequazione del pendolo
x
= sin(x) =

cos(x)
x

con la condizione iniziale


x(0) = x0 ,

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)

ma non il valore di x(dt) non `e possibile utilizzare questo algoritmo


per effettuare il primo passo di integrazione e calcolare x(dt). Questo deve essere calcolato

431

yaC-Primer: Integrazione equazione del pendolo con Verlet

(Rev. 2.1.1)

usando un algoritmo differentemente. Il seguente programma utilizza lalgoritmo di Eulero


del secondo ordine
dt2
x(dt) = x(0) + dt x(0)

+
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 ] ;

/*
/*
/*
/*
/*
/*
/*
/*

contatore passi integrazione


# massimo passi integrazione
condizione iniziale
x_new = x(t+1)
x_cur = x(t)
x_old = x(t -1)
velocit a istantanea
passo integrazione

/* nome file di output

FILE outf ;
/* Lettura dati iniziali */
read_init(&x_0 , &v_0 , &dt , &stp_max ) ;

432

*/
*/
*/
*/
*/
*/
*/
*/
*/

yaC-Primer: Integrazione equazione del pendolo con Verlet

(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

yaC-Primer: Integrazione equazione del pendolo con Verlet

(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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

utilizzando la formula delle differenze centrali



1 
v(t) =
x(t + dt) x(t dt) + O(dt3 )
2 dt
che pu`o essere facilmente ricavata utilizzando lo sviluppo di Taylor di x(t dt). Di
nuovo lordine con cui `e calcolata la velocit`a `e pi`
u basso di quello dellalgoritmo di
integrazione tuttavia anche in questo caso, siccome la velocit`a viene utilizzata solo per
il calcolo dellenergia meccanica, lordine dellintegrazione non `e modificato.
Infine si sposta il valore di x(t), x cur, sulla variabile x old e quello di x(t + dt), x new,
sulla variabile x cur in modo che la prossima iterazione la funzione verlet() restituisca
il valore di x(t + 2dt).
Per confronto con lalgoritmo di integrazione di Eulero la seguente figura riporta i valori di v
in funzione dei valori di x ottenuti con un passo di integrazione dt = 0.01 e condizione iniziale
x0 = 0.1, v0 = 0.

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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

3.7. Esempio: Moto di due pianeti con algoritmo di Verlet

(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

dove ri = (xi , yi ) `e la posizione delli-esimo pianeta in un sistema di riferimento dato, e G `e


la costante di gravitazione.

y


m1


m2

Le masse dei pianeti mi , come la loro posizione e velocit`a iniziali sono conosciute, ma non
specificate.

Nota sulle Condizioni Iniziali


Il sistema composto dai due pianeti `e isolato, di conseguenza il Centro di Massa del sistema o
`e fermo o si muove di moto rettilineo uniforme. Possiamo quindi, senza perdere di generalit`
a,
assumere come sistema di riferimento il sistema del Centro di Massa. Questa assunzione
elimina la liberta nella scelta della condizione iniziale di uno dei due pianeti, che dovr`a essere
fissata una volta nota la condizione iniziale dellaltro in modo che il Centro di Massa del
sistema si trovi nellorigine con velocit`a nulla.
Nel seguente programma si richiede quindi, oltre alle masse dei due pianeti, solo la condizione
iniziale del primo pianeta. La seconda viene fissata di conseguenza.

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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

Lerrore commesso nella discretizzazione del tempo `e O(dt4 ).


Siccome allistante iniziale sono specificate le posizioni e le velocit`a ma non la posizione
precedente, non possiamo usare lalgoritmo di Verlet. Per effettuare il primo passo useremo
quindi un algoritmo differente che utilizzi la velocit`a, come ad esempio lalgoritmo di Eulero
del secondo ordine. Lerrore commesso nella discretizzazione del tempo `e O(dt3 ) e quindi meno
preciso del precedente. Questo tuttavia non crea eccessivi problemi poich`e viene utilizzato
per compiere un solo passo.

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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

Massa pianeta 2
posizione iniziale pianeta 1
velocita iniziale pianeta 1
numero di iterazioni
- Output :

su stdout tempo , energia meccanica


su files tempo , posizione e velocita dei pianeti .
formato :
tempo x y v_x v_y

- 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

- $Id: planets -ev.c v 1.5 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 P1 " plan1.dat"
/* out -file pianeta 1
# define OUT P2 " plan2.dat"
/* out -file pianeta 2
# define G
1.0
/* Costante di Gravitazione

*/
*/
*/
*/
*/
*/
*/

/* ============================================================== */
/*
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 ;

/* Massa del pianeta


*/
/* Coordinate del pianeta */
/* Velocita del pianeta
*/

/* ============================================================== */
/*
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

yaC-Primer: Pianeti con Verlet

FILE

(Rev. 2.0.4)

open_file ( const char path , const char mode ) ;

int main ( void )


{
int
tim ;
int
tim_max ;
double
dt = DT ;
planet_t planet ;
FILE
outf_1 ;
FILE
outf_2 ;
planet = create_planet ( 2 ) ;

/* # passi di integrazione */
/* I due pianeti */

/* crea spazio per i 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

yaC-Primer: Pianeti con Verlet

(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

*/
*/
*/

*/
*/

yaC-Primer: Pianeti con Verlet

(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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

scanf ( "%d" , tm ) ;
return ;
}
# undef MIN COS

/* macro locale a questa funzione */

/* ---* 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

= 0 . 5 p[0]> mass ( p[0]> vel . x p[0]> vel . x +


p[0]> vel . y p[0]> vel . y ) ;

ene += 0 . 5 p[1]> mass ( p[1]> vel . x p[1]> vel . x +

442

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

p[1]> vel . y p[1]> vel . y ) ;


ene += (( double ) G ) p[0]> mass p[1]> mass / dist ;
return ene ;
}
/* ---* compute_force ()
*
* Calcola la forza tra i pianeti
*/
dvect_t compute_force ( planet_t p )
{
dvect_t
f ;
double
dist_x , dist_y , dist ;
f = ( dvect_t ) malloc ( 2 sizeof ( dvect_t ) ) ;
if ( f == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory for force\n" ) ;
exit ( 1 ) ;
}
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 , 1 . 5 ) ;
dist_x /= dist ;
dist_y /= dist ;
f[0].x =
f[0].y =

( ( double ) G ) p[1]> mass dist_x ;


( ( double ) G ) p[1]> mass dist_y ;

f [ 1 ] . x = (( double ) G ) p[0]> mass dist_x ;


f [ 1 ] . y = (( double ) G ) p[0]> mass dist_y ;
return f ;
}
/* variabile globale visibile da qui in poi */
dvect_t x_old [ 2 ] ;
/* ---* eulero ()
*
* x_tp1 = x_t + v_t * dt + f_t * dt ^2 / 2
*/
void eulero ( double dt , planet_t plan )
{
double
dt2 ;
dvect_t x_new [ 2 ] ;
/* variabili temporanee */
dvect_t
forza ;

443

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

dt2 = 0 . 5 dt dt ;

/* calcolata una volta invece di 4 */

/* Nuova posizione */
forza = compute_force ( plan ) ;
x_new [ 0 ] . x =
+
x_new [ 0 ] . y =
+

plan [0]> pos . x +


dt2 forza [ 0 ] . x
plan [0]> pos . y +
dt2 forza [ 0 ] . y

dt plan [0]> vel . x


;
dt plan [0]> vel . y
;

x_new [ 1 ] . x =
+
x_new [ 1 ] . y =
+

plan [1]> pos . x +


dt2 forza [ 1 ] . x
plan [1]> pos . y +
dt2 forza [ 1 ] . y

dt plan [1]> vel . x


;
dt plan [1]> vel . 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 ;

/* libera lo spazio temporaneo allocato */

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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

/* Nuova posizione */
forza = compute_force ( plan ) ;
dt2 = dt dt ;

/* calcolata una volta invece di 4 */

x_new [ 0 ] . x = 2 . 0 plan [0]> pos . x + dt2 forza [ 0 ] . x x_old [ 0 ] . x ;


x_new [ 0 ] . y = 2 . 0 plan [0]> pos . y + dt2 forza [ 0 ] . y x_old [ 0 ] . y ;
x_new [ 1 ] . x = 2 . 0 plan [1]> pos . x + dt2 forza [ 1 ] . x x_old [ 1 ] . x ;
x_new [ 1 ] . y = 2 . 0 plan [1]> pos . y + dt2 forza [ 1 ] . y x_old [ 1 ] . y ;
/* Nuova velocita */
dt2 = 2 . 0 dt ;

/* calcolata una volta invece di 4 */

plan [0]> vel . x = ( x_new [ 0 ] . x x_old [ 0 ] . x ) / dt2 ;


plan [0]> vel . y = ( x_new [ 0 ] . y x_old [ 0 ] . y ) / dt2 ;
plan [1]> vel . x = ( x_new [ 1 ] . x x_old [ 1 ] . x ) / dt2 ;
plan [1]> vel . y = ( x_new [ 1 ] . y x_old [ 1 ] . y ) / dt2 ;
/* 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 ;

/* libera lo spazio temporaneo allocato */

return ;
}


Note sul programma


# define DT 0 . 0 1
# define OUT P1 " plan1.dat"
# define OUT P2 " plan2.dat"
# define G 1 . 0

/*
/*
/*
/*

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

yaC-Primer: Pianeti con Verlet

(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 ;

/* Massa del pianeta


*/
/* Coordinate del pianeta */
/* Velocita del pianeta
*/

La prima dichiarazione definisce il tipo struttura dvect t che rappresenta un vettore in


due dimensioni mentre la seconda definisce un tipo struttura planet t e contiene tutte
le informazioni rilevanti come la massa, la posizione e la velocit`a di un pianeta e quindi
crea un oggetto pianeta.
Le definizioni sono fatte esternamente da ogni blocco in modo da poter usare questi tipi
ovunque nel programma.
planet_t create_planet ( int n ) ;
void
........

eulero ( double dt , planet_t p ) ;

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 */

Questa istruzione dichiara la variabile planet come puntatore a puntatore di un oggetto


di tipo planet t. In altre parole nella locazione di memoria puntata da planet vi
`e lindirizzo di memoria di dove si trova un oggetto di tipo planet t. Questa variabile `e usata per creare un array di puntatori ad oggetti di tipo planet t, in questo
caso unarray di dimensione 2. Lallocazione della memoria viene fatta con la funzione
create planet():
planet = create_planet ( 2 ) ;

/* crea spazio per i pianeti */

Lutilizzo di unarray di puntatori non `e strettamente necessario, ed `e usato in questo


programma al solo scopo illustrativo. Sarebbe stato possibile scrivere lo stesso programma con un array di oggetti di tipo planet t:
planet_t planet [ 2 ] ;

/* allocazione statica */

ovvero
planet_t planet ;

/* allocazione dinamica */

planet = ( planet_t ) malloc ( 2 sizeof ( planet_t ) ) ;

read_init ( planet , &tim_max ) ;

446

yaC-Primer: Pianeti con Verlet

(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 prima istruzione riserva lo spazio per un array di n puntatori ad oggetti di tipo


planet t. La seconda riserva lo spazio per un oggetto di tipo planet t ed assegna il
suo indirizzo di memoria ad un elemento dellarray di puntatori.
sscanf ( line , "%lf" , &(p[0]> mass ) ) ;
....
sscanf ( line , "%lf%lf" , &(p[0]> pos . x ) , &(p[0]> pos . y ) ) ;

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

yaC-Primer: Pianeti con Verlet

(Rev. 2.0.4)

sscanf ( line , "%lf" , &(( p [ 0 ] ) . mass ) ) ;


sscanf ( line , "%lf%lf" , &(( p [ 0 ] ) . pos . x ) , &(( p [ 0 ] ) . pos . y ) ) ;

aumentando per`o le possibilit`a di un errore di scrittura.


if ( 1 fabs ( c_angle ) < MIN_COS ) {
fprintf ( stderr , "\n Collisione tra pianeti ! \n" ) ;
exit ( 1 ) ;
}

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 */

Se lo scopo di una macro `e locale `e buona norma di programmazione limitarne la


definizione alla parte di programma rilevante. Questo elimina leventualit`a di conflitti
tra macros.
dvect_t compute_force ( planet_t p )
Questa funzione calcola la forza, divisa per la massa e quindi di fatto laccelerazione,
agente su ogni pianeta. Il valore della forza viene assegnato ad un array di due oggetti
di tipo dvect t.
La funzione `e definita prima delle funzioni eulero() e verlet() dove viene usata, per
cui non `e necessario usare un prototipo.
dvect_t x_old [ 2 ] ;

/* variabili che serve ricordare */

Lalgoritmo di Verlet ha bisogno della posizione attuale e di quella precedente, che va


quindi ricordata. La variabile x old serve a questo scopo. Questa variabile non solo deve
essere accessibile nelle funzioni eulero() e verlet(), ma non deve scomparire quando
il programma esce dalla funzione. Questo `e ottenuto definendola fuori dei blocchi.
Osserviamo che sebbene la variabile sia definita fuori dai tutti i blocchi, e quindi `e una
variabile globale, il fatto di averla definita qui fa si che la variabile non `e visibile nella
parte del programma che precede la definizione, ma solo nella parte che segue. In altre
parole x old `e una sorta di variabile privata della restante parte del programma.
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 ;

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 ;

/* calcolata una volta invece di 4 */

plan [0]> vel . x = ( x_new [ 0 ] . x x_old [ 0 ] . x ) / dt2 ;


plan [0]> vel . y = ( x_new [ 0 ] . y x_old [ 0 ] . y ) / dt2 ;
plan [1]> vel . x = ( x_new [ 1 ] . x x_old [ 1 ] . x ) / dt2 ;
plan [1]> vel . y = ( x_new [ 1 ] . y x_old [ 1 ] . y ) / dt2 ;

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.8. Integrazione di Equazioni Differenziali Ordinarie: Algoritmo


Runge-Kutta (Rev. 2.0.1)
Discutendo lalgoritmo di integrazione di Eulero si `e visto che la soluzione di equazioni differenziali ordinarie di ordine qualsiasi pu`o sempre essere ridotta a quella di un insieme di
equazioni differenziali ordinarie del primo ordine. Senza perdere di generalit`a consideriamo
quindi un insieme di equazioni differenziali ordinarie del primo ordine:
d
y(x) = f (x, y),
dx

(3.5)

449

yaC-Primer: Runge-Kutta

(Rev. 2.0.1)

dove y e f appartengono allo spazio RN :



y = y1, y2, . . . , yN ,


f = f 1, f 2, . . . , f N ,

con la condizione iniziale


y(x0 ) = y0 .

(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 .

3.8.1. Schema del secondo ordine


Gli algoritmi di Runge-Kutta costruiscono una approssimazione migliore della funzione y(x)
nei punti x = xn sfruttando ulteriori informazioni sulla funzione nellintervallo [xn , xn + h]. Il
pi`
u semplice algoritmo di Runge-Kutta sfrutta oltre alle informazioni allinizio dellintervallo
anche quelle nel punto di mezzo. Per questo lalgoritmo `e anche conosciuto come il metodo
del punto di mezzo (midpoint).
Lo schema dellalgoritmo
i
k1

k2i

i
yn+1

di Runge-Kutta midpoint `e:


= h f i (xn , yn ),
= h f i (xn + 21 h, yn + 12 k1 ),

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

Sostituendo lespressione (3.9) nellequazione (3.8) si ottiene:


Z
y(x + h) y(x) = f

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)

Questa `e lespressione esatta di y(x + h) y(x) fino allordine h2 incluso.


Osserviamo che la formula (3.10) `e essa stessa un algoritmo del secondo ordine per il calcolo di
y(x + h). Lo svantaggio di questo algoritmo rispetto, ad esempio, a quello di Runge-Kutta `e
che oltre alla conoscenza della funzione f (x, y) richiede anche la conoscenza della sue derivate
fx (x, y) e fy (x, y), che non sempre sono facilmente valutabili.
Sviluppando lo schema di Runge-Kutta (3.7) per h piccolo otteniamo:
k2 = h f + h [ 21 h fx + 21 k1 fy + O(h2 )]
= h f + 21 h2 [fx + fy f ] + O(h3 )
che sostituito nellultima delle equazioni (3.7) riproduce lo sviluppo di Taylor (3.10) fino
allordine h2 incluso mostrando cos` che lo schema di Runge-Kutta (3.7) `e uno schema del
secondo ordine.

3.8.2. Schema del quarto ordine


Il metodo Runge-Kutta pu`o essere facilmente generalizzato sfruttando pi`
u informazioni sulla
funzione nellintervallo [xn , xn + h] per ottenere algoritmi di ordine pi`
u elevato. Lalgoritmo

451

yaC-Primer: Pianeti con attrito

(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.

3.9. Esempio: Moto di due pianeti in presenza di attrito

(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

dove ri = (xi , yi ) `e la posizione delli-esimo pianeta e `e il coefficiente di attrito. Come


nel caso senza attrito scegliamo come sistema di riferimento il sistema del centro di massa.
Questo vuol dire che fissate le masse mi dei due pianeti solo la posizione e la velocit`a iniziale
di uno dei due pianeti devono essere specificate.
In presenza di attrito lenergia meccanica totale E non si conserva ma diminuisce nel tempo.
Un semplice calcolo mostra che la dissipazione di energia `e data da
d
, E = (v1 v1 + v2 v2 )
dt
dove vi = (x i , y i ) `e la velocit`a delli-esimo pianeta.
Programma: planets-frict-rk.c

452

yaC-Primer: Pianeti con attrito

(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 :

su stdout tempo , energia meccanica


su files tempo , posizione e velocita dei pianeti .
formato :
tempo x y v_x v_y

- 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

- $Id: planets -frict -rk.c v 1.1 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 P1 " plan1.dat"
/* out -file pianeta 1
*/
# define OUT P2 " plan2.dat"
/* out -file pianeta 2
*/
# define G
1.0
/* Costante di Gravitazione
*/
# define N ENE
10
/* Ogni quanto scrive energ. */
# define MIN DIS 0 . 1
/* Distanza minima pianeti
*/
/* ============================================================== */

453

yaC-Primer: Pianeti con attrito

(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 ;

/* Massa del pianeta


*/
/* Coordinate del pianeta */
/* Velocita del pianeta
*/

/* ============================================================== */
/*
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 */

/* crea spazio per il pianeta */

/* ***
* 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

yaC-Primer: Pianeti con attrito

(Rev. 2.0.2)

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 \t e_diss = %f\n" ,
energy ( planet ) , d_energy(&planet ) ) ;
/* Ora integro usando Runge -Kutta */
for ( tim = 1 ; tim < tim_max ; ++tim )
{
/* Un passo t -> t + dt */
rk4 ( dt , planet ) ;
/* scrivi sui files */
write_out ( outf_1 , dt ( double ) tim , &planet [ 0 ] ) ;
write_out ( outf_2 , dt ( double ) tim , &planet [ 1 ] ) ;
/* Controllo integrazione con energia */
if ( ! ( tim % N_ENE ) ) {
printf ( "%f: energy = %f \t e_diss = %f\n" ,
dt ( double ) tim ,
energy ( planet ) ,
d_energy(&planet ) ) ;
}
}
fclose ( outf_1 ) ;
fclose ( outf_2 ) ;
return ( 0 ) ;
}
/* ============================================================== */
/*
Routines di definizione
*/
/* ============================================================== */
/* ---* create_planet ()
*
* Crea lo spazio di memoria per un dato di tipo planet
*/
planet_ty create_planet ( int n )
{
planet_ty c ;
c = ( planet_ty ) malloc ( n sizeof ( planet_ty ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Cannot allocate space for planet \n\n" ) ;
exit ( 1 ) ;
}
return ( c ) ;

455

yaC-Primer: Pianeti con attrito

(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

yaC-Primer: Pianeti con attrito

(Rev. 2.0.2)

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 ( 9 ) ;
}
/* 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 ( "\ ngamma : %f\n" , Gamma ) ;
printf ( "\n# iterazioni (dt = %f): " , DT ) ;
scanf ( "%d" , tm ) ;
return ;
}
# undef MIN COS

/* macro locale a questa funzione */

/* ---* 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

yaC-Primer: Pianeti con attrito

(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

= 0 . 5 p [ 0 ] . mass ( p [ 0 ] . vel . x p [ 0 ] . vel . x +


p [ 0 ] . vel . y p [ 0 ] . vel . y ) ;

ene += 0 . 5 p [ 1 ] . mass ( p [ 1 ] . vel . x p [ 1 ] . vel . x +


p [ 1 ] . vel . y p [ 1 ] . vel . y ) ;
ene += (( double ) G ) p [ 0 ] . mass p [ 1 ] . mass / dist ;
return ( ene ) ;
}
/* ---* d_energy ()
*
* Calcola la dissipazione di energia
*/
double d_energy ( planet_ty p )
{
double d_ene ;
= Gamma ( ( p ) [ 0 ] . vel . x
( p ) [ 0 ] . vel . y
( p ) [ 1 ] . vel . x
( p ) [ 1 ] . vel . y
return(d_ene ) ;

d_ene

( p ) [ 0 ] . vel . x +
( p ) [ 0 ] . vel . y +
( p ) [ 1 ] . vel . x +
( p ) [ 1 ] . vel . y ) ;

/* tipo di dati usato per lintegrazione */


typedef struct {
dvect_ty pos ;
dvect_ty vel ;
} deriv_ty ;

458

yaC-Primer: Pianeti con attrito

(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

yaC-Primer: Pianeti con attrito

(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 ;

/* calcolata una volta sola */

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

yaC-Primer: Pianeti con attrito

(Rev. 2.0.2)

void rk4 ( double dt , planet_ty plan )


{
register int
ip ;
double
dt2 ;
planet_ty p_tmp ;
deriv_ty
k [ 4 ] ;
dt2 = 0 . 5 dt ;

/* calcolata una volta sola */

/* 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 ;

/* x(t+1) = x(t) + dt [k0/6 + k1/3 + k2/3 + k3 /6] */


for ( ip = 0 ; ip < 2 ; ++ip ) {
plan [ ip ] . pos . x += dt (
k [ 0 ] [ ip ] . pos . x + 2 . 0 k [ 1 ] [ ip ] . pos . x
+ 2 . 0 k [ 2 ] [ ip ] . pos . x +
k [ 3 ] [ ip ] . pos . x )
/ 6.0;
plan [ ip ] . pos . y += dt (
k [ 0 ] [ ip ] . pos . y + 2 . 0 k [ 1 ] [ ip ] . pos . y
+ 2 . 0 k [ 2 ] [ ip ] . pos . y +
k [ 3 ] [ ip ] . pos . y )
/ 6.0;
plan [ ip ] . vel . x += dt (
k [ 0 ] [ ip ] . vel . x + 2 . 0 k [ 1 ] [ ip ] . vel . x

461

yaC-Primer: Pianeti con attrito

(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 ;
}


Note sul programma


# define N ENE

10

/* Ogni quanto scrive energ.

*/

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 ) ) ;
}

# define MIN DIS 0 . 0 0 1

dt ( double ) tim ,

/* Distanza minima pianeti

*/

In presenza di attrito le orbite dei pianteti si avvicinano. Se la distanza diventa molto


piccola la forza diventa molto grande ed il codice numerico diventa instabile poich`e la
stima delle variazioni delle posizioni e velocit`a diviene poco precisa. Il valore minimo
della distanza dipende sia dallalgoritmo usato che dal passo di integrazione. Senza
entrare nei dettagli dellalgoritmo, un modo semplice per evitare questo problema `e
quello di fissare a priori una distanza minima e bloccare lintegrazione quando la distanza diventa pi`
u piccola. Alloccorrenza il valore fissato pu`o essere raffinato con prove
successive. In questo programma la distanza minima viene fissata con la macro MIN DIS.
double Gamma ;

/* coefficente gamma globale per comodita */

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

yaC-Primer: Pianeti con attrito

= 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 ) ;

La funzione d energy() prende come argomento un puntatore ad un puntatore ad una


struttura a puro scopo didattico. Siccome p `e un puntatore a puntatore, *p contiene
lindirizzo di un oggetto di tipo planet ty che in questo caso questo `e il primo di un
array di due elementi. Di conseguenza *p `e un puntatore ad un array di due strutture
di tipo planet ty i cui elementi sono (*p)[0] e (*p)[1]. Per accedere ai campi delle
strutture si usa la solita notazione con loperatore ., per cui ad esempio la componente
x della velocit`a del primo pianeta `e (*p)[0].vel.x e cos` via.
Confrontanto questo programma con il programma planets-ev.c si si nota che anche
in questultimo si erano usate funzioni che prendono parametri del tipo planet ty **p,
tuttavia in quel caso si era usata la sintassi p[0]->vel.x. Tuttavia se usassimo la
stessa sintassi in questo programma otterremmo un errore. Il motivo `e che in questo
programma *p `e un array di strutture, mentre nel programma planets-ev.c *p `e array
di puntatori a strutture. In entrambi i casi p contiene un puntatore ad un puntatore ad
una struttura, la differenza `e nellorganizzazione in memoria delle variabili.
Nel caso dellarray di strutture usato in questo programma lorganizzazione `e:
Memoria

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

In questo caso p contiene un indirizzo di memoria dove `e contenuto lindirizzo di memoria


del primo elemento di un array di strutture di tipo planet ty. Di conseguenza *p
`e il puntatore ad un array di strutture per cui, essendo gli elementi di un array in
posizioni di memoria contigue, *p punta al primo elemento, *p+1 al secondo e cos` via,
come mostrato nella figura. Per accedere agli elementi dellarray basta dereferenziare
i puntatori, per cui la prima struttura `e data da *(*p) = (*p)[0], la seconda da
*(*p+1) = (*p)[1], etc. Osserviamo che mentre per il primo elemento le parentesi sono
opzionali e avremmo potuto scrivere semplicemente **p, le parentesi per gli elementi

463

yaC-Primer: Pianeti con attrito

(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

In questo caso p contiene lindirizzo di memoria del primo elemento di un array di


puntatori a strutture di tipo planet ty quindi, dato che il puntatore p punta al primo
elemento dellarray e p+1 al secondo, *p = p[0] `e il puntatore alla prima struttura
mentre *(p+1) = p[1] alla seconda, e cos` via. Questa volta sono i puntatori alle
strutture che sono in locazioni di memoria contigue (array), mentre le strutture non
necessariamente lo sono. Per accedere alle strutture basta dereferenziare i puntatori
per cui ad esempio la prima struttura `e data da **p = *(p[0]) mentre la seconda
da **(p+1) = *(p[1]). Infine per accedere ai campi delle singole strutture si utilizza
loperatore ., come mostrato in figura.
Avendo a che fare con puntatori a strutture `e possibile utilizzare anche loperatore ->
definito come:
struct_ptr>struct_field :=

( 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

mentre nel caso di un array di puntatori a strutture:


( ( p [ 1 ] ) . mass <==> p[1]> mass

464

yaC-Primer: Oscillatore Armonico Forzato

(Rev. 2.0.4)

static double cut = MIN_DIS MIN_DIS MIN_DIS ;


....
if ( den < cut ) {
fprintf ( stdout , "\ nCollision ! my day .... my day ... \n\n" ) ;
exit ( 1 0 ) ;
}

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.

3.10. Esempio: Oscillatore Armonico Forzato

(Rev. 2.0.4)

Il seguente programma integra lequazione del moto delloscillatore armonico forzato


dx
F0
d2 x
+
+ 02 x =
sin(t)
dt2
dt
m
dove 0 `e la pulsazione caratteristica delloscillatore, il coefficiente di attrito viscoso, m
la massa delloscillatore, F0 lampiezza della forza esterna periodica ed la pulsazione della
forza esterna. Il programma pu`o utilizzare per lintegrazione sia lalgoritmo di Eulero e quello
di Runge-Kutta del secondo e del quarto ordine.
Programma: forced osc.c


/* ***************************************************************

- 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

yaC-Primer: Oscillatore Armonico Forzato

(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

yaC-Primer: Oscillatore Armonico Forzato

double
double
double
} Osc ;

gamma ;
f_0 ;
omega ;

int main ( void )


{
double time , time_max ;
snd_ord_t
point ;
FILE
outf ;
point = create_snd_ord ( ) ;

/* attrito
/* ampiezza forcing
/* frequenza forcing

(Rev. 2.0.4)

*/
*/
*/

/* punto materiale */

/* crea spazio per il punto */

/* ***********************************
* 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

yaC-Primer: Oscillatore Armonico Forzato

(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

*/
*/
*/
*/
*/
*/
*/
*/
*/

yaC-Primer: Oscillatore Armonico Forzato

(Rev. 2.0.4)

d_ene = Osc . gamma POW2 ( p>vel ) + forcing ( t ) ;


return d_ene ;
}
/* ---* power ()
*/
double power ( double t , snd_ord_t p )
{
return ( p>vel forcing ( t ) ) ;
}
/* ============================================================== */
/*
Routines di I/O
*/
/* ============================================================== */
/* ---* read_init ()
*
* legge la condizione iniziale ed i parametri
*/
void read_init ( snd_ord_t p , double tm )
{
char line [ 8 1 ] ;
printf ( "\ nOscillatore smorzato con forza esterna : \n\n" ) ;
printf ( " Massa
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(Osc . mass ) ) ;

: ");

printf ( " Frequenza ( omega_0 )


: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(Osc . omega_0 ) ) ;
printf ( " Attrito (gamma )
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(Osc . gamma ) ) ;
printf ( " Ampiezza forcing
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(Osc . f_0 ) ) ;

: ");

printf ( " Frequenza forcing ( omega) : " ) ;


fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(Osc . omega ) ) ;
printf ( " Posizione iniziale x
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , &(p>pos ) ) ;

: ");

printf ( " Velocita iniziale v


fgets ( line , sizeof ( line ) , stdin ) ;

: ");

469

yaC-Primer: Oscillatore Armonico Forzato

(Rev. 2.0.4)

sscanf ( line , "%lf" , &(p>vel ) ) ;


printf ( "\n# passi integrazione (dt = %6.5f): " , DT ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lf" , tm ) ;
tm = ( double ) DT ;
return ;
}
/* ============================================================== */
/*
Routines di Integrazione
*/
/* ============================================================== */
snd_ord_t create_snd_ord ( void )
{
snd_ord_t c ;
c = ( snd_ord_t ) malloc ( sizeof ( snd_ord_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory \n" ) ;
exit ( 1 ) ;
}
return c ;
}
/* ********************************************
*
* Eulero
*
* \dot x = f(t,x)
* x(t+1) = x(t) + dt * f(t,x(t) + O(dt2)
*
* Input: time
*
point
*
funzione che calcola f
*
* Output : nuove coordinate point
*
nuovo tempo
*
******************************************** */
void eul ( double t , snd_ord_t p ,
void deriv ( double , snd_ord_t , snd_ord_t ) )
{
static double
dt = DT ;
static snd_ord_t der ;
deriv ( t , p , &der ) ;
p>pos += dt der . pos ;
p>vel += dt der . vel ;

470

yaC-Primer: Oscillatore Armonico Forzato

(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

yaC-Primer: Oscillatore Armonico Forzato

(Rev. 2.0.4)

* k4 = f(t + dt , x(t) + dt*k3)


* x(t+1) = x(t) + k1/6 + k2/3 + k3/3 + k4/6 + O(dt ^5)
*
* Input: time
*
point
*
funzione che calcola f
*
* Output : nuove coordinate point
*
nuovo tempo
*
******************************************************** */
void rk4 ( 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 k1 , k2 , k3 , k4 ;
static snd_ord_t x_tmp ;
deriv ( t , p , &k1 ) ;
t += dt_2 ;
x_tmp . pos = p>pos + dt_2 k1 . pos ;
x_tmp . vel = p>vel + dt_2 k1 . vel ;
deriv ( t , &x_tmp , &k2 ) ;
x_tmp . pos = p>pos + dt_2 k2 . pos ;
x_tmp . vel = p>vel + dt_2 k2 . vel ;
deriv ( t , &x_tmp , &k3 ) ;
t += dt_2 ;
x_tmp . pos = p>pos + dt k3 . pos ;
x_tmp . vel = p>vel + dt k3 . vel ;
deriv ( t , &x_tmp , &k4 ) ;
p>pos += dt ( k1 . pos + 2 . 0 k2 . pos + 2 . 0 k3 . pos + k4 . pos ) / 6 . 0 ;
p>vel += dt ( k1 . vel + 2 . 0 k2 . vel + 2 . 0 k3 . vel + k4 . vel ) / 6 . 0 ;
return ;
}


Note sul programma forced osc.c


# define POW2( x ) ( ( x ) ( x ) )
Usiamo una macro per definire il quadrato di una variabile. Il preprocessore C sostituisce
a ogni occorrenza di POW2(x) la stringa ((x)*(x)), per cui ad esempio nellistruzione
der>vel = osc . gamma var>vel
POW2 ( Osc . omega_0 ) var>pos

472

yaC-Primer: Studio Oscillatore Forzato

(Rev. 2.0.1)

+ forcing ( t ) ;

POW2(Osc.omega 0) viene sostituita da ((Osc.omega 0)*(Osc.omega 0)), per cui


listruzione passata al compilatore `e
der>vel = osc . gamma var>vel
( ( Osc . omega_0 ) ( Osc . omega_0 ) ) var>pos
+ forcing ( t ) ;

struct {
double
double
double
double
double
} Osc ;

mass ;
omega_0 ;
gamma ;
f_0 ;
omega ;

/*
/*
/*
/*
/*

massa dell oscillatore


omega_0 dell oscillatore
attrito
ampiezza forcing
frequenza forcing

*/
*/
*/
*/
*/

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

Luso delloperatore defined() del preprocessore C in congiunzione con i comandi #if,


#elif permette di scegliere mediante flag -D del compilatore, e quindi direttamente
in fase di compilazione senza dover modificare il programma, quale algoritmo di integrazione utilizzare. Per utilizzare lalgoritmo di Eulero si specificher`a -DEULER mentre
-DRK2 selezioner`a lalgoritmo di Runge-Kutta al secondo ordine. Se non si specifica
nulla il programma utilizza lalgoritmo di Runge-Kutta del quarto ordine (default).

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

yaC-Primer: Studio Oscillatore Forzato

(Rev. 2.0.1)

3.11. Esempio: Studio dellOscillatore Armonico Forzato da una


Sinusoide (Rev. 2.0.1)
Lequazione di un oscillatore armonico forzato con una forzante sinusoidale `e
d2 x
dx
F0
+
+ 02 x =
sin(t)
(3.11)
2
dt
dt
m
dove 0 `e la pulsazione caratteristica delloscillatore, il coefficiente di attrito viscoso, m
la massa delloscillatore, F0 lampiezza della forza esterna periodica ed la pulsazione della
forza sinusoidale esterna.

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

yaC-Primer: Studio Oscillatore Forzato

(Rev. 2.0.1)

Risolvendo per A e si ha:


F0
1
p
2
m (0 2 )2 + 2 2

2
0 2

A =
tg =

Potenza Media fornita dalla Forza


La forza esterna F (t) fornisce una potenza istantanea
P (t) = F (t) v(t)
Nel regime stazionario, ossia quando il transiente `e scomparso e loscillatore oscilla con la
frequenza della forza sinusoidale esterna, P (t) `e data da
P (t) = F0 sin(t) A cos(t + )
In un ciclo di periodo T la forza fornisce una potenza media pari a
1
P =
T

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

yaC-Primer: Studio Oscillatore Forzato

(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

Per controllare la precisione dellalgoritmo numerico consideriamo il caso di un oscillatore


libero = F0 = 0 con 0 = 3 e condizione iniziale x(0) = 1 e v(0) = 0. Le equazioni sono
state integrate sia usando lalgoritmo di Eulero che quello di Runge-Kutta del secondo e del
quarto ordine. Per tutti gli schemi si `e usato un passo di integrazione dt = 0.01. I risultati
sono riportati nelle figure seguenti.

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

Oscillatore libero: v in funzione di x

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

yaC-Primer: Studio Oscillatore Forzato

(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

Oscillatore libero: Energia meccanica in funzione del


tempo

Gli algoritmi di Runge-Kutta appaiono molto pi`


u stabili, tuttavia vi `e una differenza anche tra questi. Infatti guardando lenergia su una scala pi`
u fine si nota che anche nel caso
dellalgoritmo di Runge-Kutta del secondo ordine lenergia aumenta, anche se non drammaticamente come ne caso dellalgoritmo di Eulero.

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

Oscillatore libero: Energia meccanica in funzione del


tempo con gli algoritmi di Runge-Kutta

477

yaC-Primer: Studio Oscillatore Forzato

(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

Oscillatore smorzato: x in funzione del tempo. Il valore


di `e ottenuto dalla soluzione analitica.

m = 1, 0 = 3, = 1, f0 = 0, x(0) = 1, v(0) = 0
3

-1

-2

-3
-1

-0.5

0.5

Oscillatore smorzato: v in funzione di x.

478

yaC-Primer: Studio Oscillatore Forzato

(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

Oscillatore forzato: x in funzione del tempo.


m = 1, 0 = 3, = 1, f0 = 1, x(0) = 1, v(0) = 0
2
=2
=3
=4

-1

-2

-3
-1

-0.5

0.5

Oscillatore forzato: v in funzione di x.


Quando il sistema raggiunge lo stato stazionario lenergia media del sistema rimane costante.
Questo vuol dire che la potenza media fornita in un ciclo dalla forza esterna viene tutta
dissipata dallattrito.

479

yaC-Primer: Studio Oscillatore Forzato

(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

Oscillatore forzato: Energia in funzione del tempo.


Lenergia istantanea varia, ma la media dellenergia
su un periodo della forza esterna raggiunge un valore
costante.
m = 1, 0 = 3, = 1, f0 = 1, x(0) = 1, v(0) = 0, = 2
2

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

yaC-Primer: Due molle accoppiate

(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 .

3.12. Esempio: Moto di un punto soggetto ad una forza centrale


generata da due molle (Rev. 2.0)
Questo programma calcola numericamente la traiettoria del moto di punto materiale di massa
m soggetto ad una forza centrale generata da potenziale:
V (x, y) =


k 2
x + y 2 + 2hxy ,
2

1 h 1

utilizzando lalgoritmo di Runge-Kutta del quarto ordine.


Si vuole inoltre verificare numericamente che per i valori
h=

n2 m2
,
n2 + m2

n, m = 1, 2, . . .

le orbite sono chiuse, e quindi periodiche, ed in questo caso calcolarne numericamente il


periodo.
Controllo periodicit`
a
Se lorbita `e periodica la distanza tra un punto qualsiasi della traiettoria nello spazio delle fasi
ed uno che si muove su di essa si azzerara ad intervalli di tempo regolare quando i due punti
coincidono. Siccome il moto `e deterministico `e vero anche il contrario, ossia se la distanza

481

yaC-Primer: Due molle accoppiate

(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

yaC-Primer: Due molle accoppiate

(Rev. 2.0)

V(x,y) = (k/2) ( x^2 + y^2 + 2 h x y )


Controlla se lorbita e periodica e nel
caso ne calcola il perido
Integrazione con Runge -Kutta 4 ordine
Usa il modulo yac_utils .c
- Input : massa , h , k, x(0), y(0) , vx(0) , vy (0)
- Output : sul file OUT_F:
tempo x y vx vy energia ( distanza punto iniziale )
- Parametri :

DT:
OUT_F:
CUT_OFF :
DIM:

Passo di integrazione
File output
Cutoff calcolo periodo .
Dimensioni dello spazio

- $Id: two_springs .c v 1.1 15.11.03 AC


**************************************************************** */
# include < s t d l i b . h>
# include <s t d i o . h>
# include <math . h>
# include " yac_utils .h"
/* Macros / Parametri */
# define DT
0.01
# define OUT F
"two -spr.dat"
# define CUT OFF 0 . 0 1
# define DIM
2

/*
/*
/*
/*

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

yaC-Primer: Due molle accoppiate

(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 ) ;

/* crea il punto run -time


*/
/* e quello di riferimento
*/
/* per controllo orbita chiusa */

/* ***
* 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

yaC-Primer: Due molle accoppiate

(Rev. 2.0)

printf ( "\ nTrovati %d periodi . Periodo medio: %f (var: %f)\n\n" ,


n_period ,
period_me ,
sqrt ( fabs ( period_sq POW2 ( period_me ) ) )
);
}
else {
printf ( "\ nHumm ..... , la traiettoria non sembra periodica \n\n" ) ;
}
free ( point_rt ) ;
free ( point_ref ) ;
fclose ( outf ) ;
return ( 0 ) ;
}
/* ==============================================================
/*
Routines Fisica
/*
/* deriv ()
--> forza / massa
/* energy () --> energia meccanica
/* ==============================================================
/* ---* deriv ()
*/
void deriv ( double t , point_t p , point_t d )
{
d>pos [ 0 ] =
p>vel [ 0 ] ;
d>vel [ 0 ] = param . k_m ( p>pos [ 0 ] + param . h p>pos [ 1 ] ) ;

*/
*/
*/
*/
*/
*/

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

yaC-Primer: Due molle accoppiate

(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

: ");

yaC-Primer: Due molle accoppiate

printf ( "\n h
scanf ( "%lf" , &(param . h ) ) ;

(Rev. 2.0)

: ");

printf ( "\n posizione iniziale (x,y) : " ) ;


scanf ( "%lf%lf" , &(p>pos [ 0 ] ) , &(p>pos [ 1 ] ) ) ;
printf ( "\n velocita iniziale (x,y) : " ) ;
scanf ( "%lf%lf" , &(p>vel [ 0 ] ) , &(p>vel [ 1 ] ) ) ;
printf ( "\n # passi integrazione (dt = %6.5f): " , DT ) ;
scanf ( "%lf" , tm ) ;
tm = ( double ) DT ;
return ;
}
/* ============================================================== */
/*
Funzioni per gli oggetti della classe point
*/
/* ============================================================== */
/*
Funzioni :
point_t * point_new (int n)
crea un oggetto della classe point
void point_copy ( point_t *src , point_t *dest , int n)
copia src su dest.
double point_dist ( point_t *p, point_t *q, int n)
calcola la distanza tra due oggeti
n = dimesione dello spazio
*/
/* ---* point_new ()
*/
point_t point_new ( int n )
{
point_t c ;
c = ( point_t ) malloc ( sizeof ( point_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory \n" ) ;
exit ( 1 ) ;
}
c>pos = dvect ( n ) ;
c>vel = dvect ( n ) ;
return ( c ) ;
}

487

yaC-Primer: Due molle accoppiate

(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

yaC-Primer: Due molle accoppiate

(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 ;
}


Note sul programma

489

yaC-Primer: Due molle accoppiate

(Rev. 2.0)

typedef struct {
double pos ;
double vel ;
} point_t ;

/* x
*/
/* v = \dot x */

Definiamo un oggetto di tipo point t che contiene le posizioni e velocit`a di un punto


materiale nello spazio.
point_t point_new
void
double

point_copy
point_dist

( int n ) ;
( point_t scr , point_t dst , int n ) ;
( point_t p , point_t q , int n ) ;

Funzioni che operano su oggetti del tipo point t.


struct {
double
double
double
} param ;

m;
k_m ;
h;

/* v(x,y) = (k/2) ( x^2 + y^2 + 2 h x y) */


/* :== k / m
*/

` definita con scopo


La struttura param contiene tutti i parametri fisici del problema. E
globale per comodit`a.
point_copy ( point_rt , point_ref , DIM ) ;
La condizione iniziale `e sicuramente un punto sulla traiettoria e quindi la distanza con il
punto che si muove sulla traiettoria, se questa `e periodica, deve andare periodicamente
(quasi) a zero.
dist = point_dist ( point_rt , point_ref , DIM ) ;
n_period = check_period ( time , dist , &period_me , &period_sq ) ;

Ad ogni passo si calcola la distanza con il punto di riferimento e si effettua il controllo


sulleventuale periodicit`a della traiettoria con la funzione check period() che prende
come parametri il tempo time e la distanza dist. La funzione ritorna il numero di
periodi trovati fino al tempo time e nelle variabili period me e period sq il valore
medio e quadratico medio del periodo dei periodi trovati. Se non sono state trovati
periodi fino al tempo time la funzione ritorna il valore 0.
if ( n_period ) {
printf ( "\ nTrovati %d periodi . Periodo medio: %f (var: %f)\n\n" ,
n_period ,
period_me ,
sqrt ( fabs ( period_sq POW2 ( period_me ) ) )
);
}
else {
printf ( "\nHumm ..... , la traiettoria non sembra periodica \n\n" ) ;
}

Se lorbita `e periodica stampa il numero di periodi trovati, il periodo medio e la fluttuazione per controllare la precisione del periodo calcolato.

490

yaC-Primer: Due molle accoppiate

(Rev. 2.0)

Note sulla funzione check period()


# define CUT OFF 0 . 0 1
...
static int

found

/* Cut_off calcolo periodo

*/

= 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

yaC-Primer: Due molle accoppiate

(Rev. 2.0)

lultimo valore del minimo trovato `e il minimo. Di conseguenza si aumenta di uno il


contatore dei periodi trovati n period e si calcola il valore medio ed il valore quadratico
medio degli intervalli di tempo tra due minimi successivi trovati. Infine si rimette il
valore di dist min ad un valore iniziale maggiore di CUT OFF per poter calcolare il
prossimo minimo, si salva il valore di time min su time pre e si assegna il valore 1 a
found perch`e il minimo `e stato trovato.
Nota sulla media
Le medie sono calcolate run-time con la formula
hxiN = hxiN 1 +


1 
x hxiN 1 )
N

dove hxin `e la media calcolata su n valori


n
1 X
hxin =
xi
n
i=1

facile verificare che per n = N le due formule coicidono.


E
Note sulla soluzione analitica
Le equazioni del moto del punto materiale di massa m nel campo centrale di di potenziale
V (x, y) =

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:

x(t) + y(t) = 2 A cos(+ t + + )


x(t) y(t) = 2 B cos( t + )
ovvero
x(t) = A cos(+ t + + ) + B cos( t + )
y(t) = A cos(+ t + + ) B cos( t + )

492

yaC-Primer: Due molle accoppiate

(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, . . .

e il periodo dellorbita vale


T = n T + = m T

con

T =

2
= 2

m
k (1 h)

493

yaC-Primer: Due molle accoppiate

494

(Rev. 2.0)

4. Strutture Dati
4.1. Single Linked Lists

(Rev. 2.0.2)

La possibilit`a del linguaggio C di manipolare direttamente i puntatori permette di creare


ed utilizzare strutture di dati dinamiche che possono crescere o diminuire a secondo delle
necessit`a.
Lelemento tipico di una struttura di dati dinamica, chiamato anche nodo, contiene sia le
informazioni necessarie per potersi concatenare agli altri nodi della struttura sia i dati veri e
propri. Possiamo quindi rappresentare schematicamente un nodo come:
nodo
index
data

dove
index
data

informazioni per il concatenamento con gli agli altri nodi


dati memorizzati nel nodo

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 ;
};

/* puntatore nodo successivo */


/* Dati contenuti nel nodo
*/

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

yaC-Primer: Single Linked Lists

(Rev. 2.0.2)

node_1 . next = &node_2 ;


node_2 . next = NULL ;

che creano la lista:


node_1

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;

Definizione della lista


Per prima cosa si deve definire la struttura della lista. Ad esempio
struct sgle_link {
struct sgle_link next ;
/* puntatore nodo successivo */
int
data ;
/* dato tipo int
*/
};
typedef struct sgle_link sgle_link_t ;

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

/* puntatore nodo successivo */


/* dato tipo int
*/

yaC-Primer: Single Linked Lists

(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.

Creazione della lista


La lista viene creata definendo una variabile che punta al primo elemento della lista
struct sgle_link first = NULL

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

yaC-Primer: Single Linked Lists

(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

....

Assegnazione del valore al nuovo elemento;


new_entry>data = item ;

Inserimento del nuovo elemento al primo posto della lista:


new_entry>next = first ;

new_entry

*first

next

next

data

data

....

Aggiornamento dellinizio della lista:


first = new_entry ;

*first
next

next

data

data

....

Osserviamo che linserimento di un nuovo elemento allinizio di un array richiede un tempo


proporzionale al numero si elementi presenti nellarray perch`e bisogna spostare tutti gli elementi presenti di una posizione prima di poterne inserire uno nuovo. Su una lista invece il
tempo `e indipendente dal numero di elementi della lista.

498

yaC-Primer: Single Linked Lists

(Rev. 2.0.2)

Inserimento condizionato: Liste ordinate


Gli elementi vengono aggiunti allinterno della lista in una posizione che dipende dal loro
valore. La procedura `e pi`
u complessa di quella per linserimento incondizionato in quanto
dopo aver creato il nuovo elemento bisogna determinarne la posizione nella lista in base al
valore contenuto ed inserirlo aprendo e richiudendo la lista.
La seguente funzione illustra la procedura inserendo un nuovo elemento in una lista di tipo
sgle link t in modo che gli elementi della lista risultino ordinati in ordine crescente.
La funzione add ord sgl list() prende come parametri il puntatore al primo elemento di
una lista di tipo sgle link t ed il valore da aggiungere alla lista e ritorna il puntatore al
primo elemento della lista dopo laggiunta del nuovo elemento.
Funzione: add ord sgl list()


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
*/

/* Crea il nuovo elemento */


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 ;
/* ***************************************** */
/* Se la lista e vuota o il dato e il
*/
/* piu piccolo mettilo per primo
*/
/* ***************************************** */
if ( ( first == NULL ) | | ( item <= first>data ) ) {
new_entry>next = first ;
first
= new_entry ; /* migliora la lettura
*/
return first ;
/* equivalente return new_entry */
}
/* ***************************************** */
/* altrimenti trova il punto di inserzione */
/* ***************************************** */
prev_entry = first ;
next_entry = prev_entry>next ;
while ( next_entry != NULL ) {
if ( item <= next_entry>data ) break ;
prev_entry = next_entry ;
next_entry = prev_entry>next ;
}

499

yaC-Primer: Single Linked Lists

(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 */

in programma con molta probabilit`a terminerebbe con un messaggio di errore.


Se la lista non `e vuota ed il valore da inserire non `e pi`
u piccolo di quelli gi`a contenuti
nella lista si deve scorrere la lista per cercare il punto di inserimento. Una volta trovato
la lista va tagliata e, dopo aver inserito il nuovo elemento, ricongiunta. A questo
scopo si usano due puntatori ausiliari per identificare i due elementi successivi della lista
tra i quali va inserito il nuovo elemento:
prev_entry = first ;
next_entry = prev_entry>next ;
while ( next_entry != NULL )
{
if ( item <= next_entry>data ) break ;
prev_entry = next_entry ;
next_entry = prev_entry>next ;
}

500

yaC-Primer: Single Linked Lists


prev_entry

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

Se il ciclo `e terminato perch`e la condizione `e stata verificata lelemento puntato da


next entry `e pi`
u grande (o uguale) del nuovo valore mentre quello precedente `e pi`
u
piccolo.
Nel caso invece che si sia raggiunta la fine della lista next entry `e il puntatore NULL,
mentre prev entry punta allultimo elemento della lista. In questo caso il nuovo elemento viene correttamente aggiunto alla fine della lista come ultimo elemento.
La funzione add ord sgl list() ritorna il puntatore allinizio della lista dopo che `e stato
inserito il nuovo elemento. Come esercizio si consiglia di riscrivere la funzione in modo sia di
tipo void e prenda gli stessi argomenti della funzione add sgl list().

Conteggio del numero di elementi della lista


Per contare gli elementi della lista basta scorrerla tutta fino alla fine per cui la funzione `e
molto semplice:

501

yaC-Primer: Single Linked Lists

(Rev. 2.0.2)

Funzione: cnt sgl list()


int cnt_sgl_list ( sgle_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 ;
}


Test delle funzioni


Luso delle delle funzioni add sgl list(), add ord sgl list() e cnt sgl list() `e illustrato
dal seguente programma.
Programma: test-add sgl list()

# include <s t d i o . h>
# include < s t d l i b . h>

typedef struct sgle_link {


struct sgle_link next ;
int
data ;
} sgle_link_t ;

/* puntatore nodo successivo */


/* dato tipo int
*/

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

yaC-Primer: Single Linked Lists

(Rev. 2.0.2)

printf ( "\n Dati immessi : \n" ) ;


for ( i = 0 ; i < 1 0 ; ++i ) {
data = ( int ) ( 1 0 0 . 0 rand ( ) / ( RAND_MAX + 1 . 0 ) ) ;
printf ( " input data [%d]: %3d --> " , i , data ) ;
add_sgl_list(&list , data ) ;
ord_list = add_ord_sgl_list ( ord_list , data ) ;
printf ( "# elementi lista: non -ord = %2d ord = %2d\n" ,
cnt_sgl_list ( list ) ,
cnt_sgl_list ( ord_list ) ) ;
}
printf ( "\n Le liste contengono : %d dati\n" , cnt_sgl_list ( list ) ) ;
printf ( "\n Non ordinata \t\t Ordinata \n\n" ) ;
list_1 = list ;
list_2 = ord_list ;
i = 0;
while ( ( list_1 != NULL ) && ( list_2 != NULL ) ) {
printf ( "list [%d] = %2d \t -- \t ord_list [%d] = %d\n" ,
i , list_1>data , i , list_2>data ) ;
list_1 = list_1>next ;
list_2 = list_2>next ;
++i ;
}
return 0 ;
}
# include " add_sgl_list .c"
# include " add_ord_sgl_list .c"
# include " cnt_sgl_list .c"


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

yaC-Primer: Single Linked Lists

input data [ 8 ] :
input data [ 9 ] :

(Rev. 2.0.2)

32 > # elementi lista : nonord = 9 ord = 9


27 > # elementi lista : nonord = 10 ord = 10

Le liste contengono : 10 dati


Non ordinata
list [ 0 ]
list [ 1 ]
list [ 2 ]
list [ 3 ]
list [ 4 ]
list [ 5 ]
list [ 6 ]
list [ 7 ]
list [ 8 ]
list [ 9 ]

=
=
=
=
=
=
=
=
=
=

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 ;

/* Inizia dal primo elemento */

while ( curr_entry != NULL ) {


if ( curr_entry>data == item )
break ;

/* stop se finita la lista

*/

/* stop se trovato elemento

*/

curr_entry = curr_entry>next ;
}
return ( curr_entry == NULL ) ;

/* 0 trovato - 1 non trovato */

}


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

pop prende un elemento dalla cima della lista:

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 ()

->
->
->
->
->

Inizializza lo Stack (NULL se fallisce )


Aggiunge un elemento
Prende un elemento
Valore primo elemento
# elementi stack , 0 se vuoto.

stack_data

-> tipo di dati dello Stack.

- $Id: stack.h v 2.0 21.10.04 AC


**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
/*
* Tipo dati dello Stack
*/
typedef char 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
*/

/* inizio dello stack


*/
/* contatore elementi stack */

/*
* 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 ;

/* inizio dello stack


*/
/* contatore elementi stack */

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.

Test dello stack


Il seguente programma illustra luso del modulo stack.c scrivendo al contrario una stringa
letta da terminale.
Programma: test-stack.c


/* ***************************************************************
- 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

yaC-Primer: Double Linked Lists

(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 ;
}


Il programma deve essere compilato come


$ cc teststack . c stack . c

o come
$ cc c teststack . c
$ cc c stack . c
$ cc teststack . o stack . o

In entrambi i casi leseguibile si trova nel file a.out.


Quando il programma viene eseguito si ottiene
Stringa
: Test dello stack
Stringa invertita : kcats olled tseT

511

yaC-Primer: Double Linked Lists

4.3. Double Linked Lists

(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 ;

/* puntatore nodo successivo */


/* puntatore nodo precedente */
/* Dati contenuti nel nodo
*/

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.

Inserimento condizionato: Liste ordinate


Consideriamo ad esempio la funzione che inserisce un nuovo elemento in una double linked
list che contiene valori di tipo double in modo tale che la lista risulti ordinata per valore
crescente dei suoi elementi.
Funzione: add ord dbl list()


void add_ord_dbl_list ( dble_link_t first , double data )
{
dble_link_t
new ;
/* nuovo elemento
*/

512

yaC-Primer: Double Linked Lists

dble_link_t insert ;

(Rev. 2.0.2)

/* punto di inserimento */

/* Crea nuovo elemento */


new = ( dble_link_t ) malloc ( sizeof ( dble_link_t ) ) ;
if ( new == NULL ) {
fprintf ( stderr , "\n Failed to allocate memory \n" ) ;
exit ( 1 ) ;
}
new>data = data ;
/* ***************************************** */
/* Se la lista e vuota o il dato e il
*/
/* piu piccolo mettilo per primo
*/
/* ***************************************** */
if ( ( first == NULL ) | | ( data <= ( first)>data ) ) {
new>next = first ;
new>prev = NULL ;
if ( first != NULL ) ( first)>prev = new ;
first
= new ;
return ;
}
/* ***************************************** */
/* altrimenti trova il punto di inserzione */
/* ***************************************** */
insert = first ;
while ( insert>next != NULL ) {
if ( data <= insert>next>data )
break ;
insert = insert>next ;
}
/* Inserisci dopo insert */
new>next = insert>next ;
new>prev = insert ;
/* Aggiorna il link in avanti dell elemento precedente */
insert>next = new ;
/* Aggiorna il link indietro dell elemento successivo */
if ( new>next != NULL ) {
new>next>prev = new ;
}
return ;
}


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

yaC-Primer: Double Linked Lists

(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

2. Aggiornamento link elemento successivo (se esiste)


if ( first != NULL ) ( first)>prev =

new ;

Il campo prev del primo elemento della lista punta al nuovo elemento.

NULL

514

new

*first

next

next

prev

prev

data

data

yaC-Primer: Double Linked Lists

(Rev. 2.0.2)

3. Aggiornamento inizio lista


first

new ;

Il nuovo elemento diventa il nuovo inizio della lista.


*first

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

yaC-Primer: Double Linked Lists

(Rev. 2.0.2)

insert

insert>next

next

next

prev

prev

data

new

data

next
prev
data

2. Aggiornamento link elemento precedente


insert>next = new ;

Lelemento successivo a insert `e adesso il nuovo elemento.


insert

new>next

next

next

prev

prev

data

new

data

next
prev
data

3. Aggiornamento link elemento successivo (se esiste)


if ( new>next != NULL ) {
new>next>prev = new ;
}

Se insert non `e lultimo elemento della lista, il campo prev dellelemento successivo a new deve puntare al nuovo elemento new.

516

yaC-Primer: Double Linked Lists


insert

new>next

next

next

prev

prev

data

new

(Rev. 2.0.2)

data

next
prev
data

Conteggio del numero di elementi della lista


Il conteggio degli elementi di una double linked list si effettua come per una single linked list:
si scorre tutta la lista fino alla fine e si contano gli elementi.
Funzione: cnt dbl list()


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.

Test delle funzioni


Il seguente codice illustra luso delle funzioni add ord dbl list() e cnt dbl list().

517

yaC-Primer: Double Linked Lists

(Rev. 2.0.2)

Programma: test-add dbl list()



# include <s t d i o . h>
# include < s t d l i b . h>

typedef double data_t ;

/* tipo dati lista */

typedef struct double_link {


struct double_link next ;
struct double_link prev ;
data_t
data ;
} dble_link_t ;

/* puntatore nodo successivo */


/* puntatore nodo precedente */
/* Dati contenuti nel nodo
*/

void add_ord_dbl_list ( dble_link_t first , double item ) ;


int cnt_dbl_list ( dble_link_t first ) ;
int main ( void )
{
int
i;
double
data ;
dble_link_t
list = NULL ;
dble_link_t list_1 ;
srand ( 1 2 3 4 5 ) ;
printf ( " elementi iniziali nella lista: %d\n" ,
cnt_dbl_list ( list ) ) ;
printf ( "\nDati immessi :\n" ) ;
for ( i = 0 ; i < 1 0 ; ++i ) {
data = 1 0 0 . 0 rand ( ) / ( RAND_MAX + 1 . 0 ) ;
printf ( " input data [%d]: %f\n" , i , data ) ;
add_ord_dbl_list(&list , data ) ;
}
printf ( "\nLa lista contiene : %d dati\n" , cnt_dbl_list ( list ) ) ;
list_1 = list ;
/* puntatore ausiliario per scorrere la lista */
i = 0;
while ( list_1 != NULL ) {
printf ( "list [%d] = %f \n" , i , list_1>data ) ;
list_1 = list_1>next ;
++i ;
}
return 0 ;
}
# include " add_ord_dbl_list .c"
# include " cnt_dbl_list .c"


Lo scopo di questo programma `e quello di provare le funzioni di conseguenza, anche se non


`e una buona pratica di programmazione, queste sono state incluse utilizzando il comando

518

yaC-Primer: Alberi binari

(Rev. 2.0.3)

#include del preprocessore.


Quando il programma viene eseguito si ottiene:
elementi iniziali nella lista : 0
Dati immessi :
input data [ 0 ] :
input data [ 1 ] :
input data [ 2 ] :
input data [ 3 ] :
input data [ 4 ] :
input data [ 5 ] :
input data [ 6 ] :
input data [ 7 ] :
input data [ 8 ] :
input data [ 9 ] :

17.839530
39.967747
16.659879
21.212187
6.193571
5.414984
27.566549
4.775724
32.103319
27.273368

La lista contiene : 10 dati


list [ 0 ] = 4 . 7 7 5 7 2 4
list [ 1 ] = 5 . 4 1 4 9 8 4
list [ 2 ] = 6 . 1 9 3 5 7 1
list [ 3 ] = 1 6 . 6 5 9 8 7 9
list [ 4 ] = 1 7 . 8 3 9 5 3 0
list [ 5 ] = 2 1 . 2 1 2 1 8 7
list [ 6 ] = 2 7 . 2 7 3 3 6 8
list [ 7 ] = 2 7 . 5 6 6 5 4 9
list [ 8 ] = 3 2 . 1 0 3 3 1 9
list [ 9 ] = 3 9 . 9 6 7 7 4 7

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.

4.4. Alberi Binari

(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

yaC-Primer: Alberi binari

(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

La struttura del nodo di un albero binario `e quindi della forma:


struct node {
struct node left ;
struct node right ;
int
data ;

/* 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

yaC-Primer: Alberi binari

(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

Il nodo Top contiene il valore 17: 13 < 17 vai a sinistra;


Il nodo di primo livello contiene il valore 10: 13 > 10 vai a destra;
Il nodo di secondo livello contiene il valore 15: 13 < 15 vai a sinistra;
Lalbero `e vuoto NULL: Crea un nuovo nodo di terzo livello ed assegnagli il valore
13.

521

yaC-Primer: Alberi binari

(Rev. 2.0.3)

Il percorso lungo lalbero `e quindi:


Top

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

yaC-Primer: Alberi binari

b_tree_data

(Rev. 2.0.3)

-> tipo di dati dell albero

- $Id: b_tree .h v 1.2 23.10.04 AC


**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
typedef double b_tree_data ;
typedef struct node {
struct node left ;
struct node right ;
b_tree_data
data ;
} node_t ;

/* Dati contenuti nel nodo */

/* nodo a sinistra */
/* nodo a destra
*/
/* dato del nodo
*/

extern void b_tree_enter ( node_t node , b_tree_data data ) ;


extern void b_tree_print ( node_t top ) ;
extern int b_tree_size ( node_t top ) ;


Le funzioni sono definite nel modulo b tree.c.


Modulo: b tree.c


/* ***************************************************************
- 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

yaC-Primer: Alberi binari

(Rev. 2.0.3)

/* 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 ;
}
/* Il nodo non e alla fine dell albero
*/
/* Controlla se inserire a sinistra o a destra */
( data <= ( node)>data ) ?
b_tree_enter ( &(node)>left , data ) :
b_tree_enter ( &(node)>right , data ) ;
return ;
}
/* --* b_tree_print ()
*
* Stampa il contenuto dell albero
*/
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 ;
}
/* --* b_tree_size ()
*
* # nodi dell albero
*/
extern int b_tree_size ( node_t top )
{
int n = 0 ;
size_b_tree ( top , &n ) ;
return n ;
}
/* --* size_b_tree ()

524

yaC-Primer: Alberi binari

(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

yaC-Primer: Alberi binari

(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 ;

( data <= ( node)>data ) ?


b_tree_enter ( &(node)>left , data ) :
b_tree_enter ( &(node)>right , data ) ;

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

Se il valore di expr1 `e diverso da zero (true) viene valutata lespressione expr2


altrimenti, se valore di expr1 `e nullo (false), viene valutata lespressione expr3.
In ogni caso solo una delle due espressioni expr2 e expr3 viene valutata. Il valore
delloperatore `e il valore delespressione valutata.
Nel nostro caso expr1 `e vera se il valore del nuovo dato `e minore o uguale al valore
del dato contenuto nel nodo. Se questo `e il caso viene valutata expr2 ed il nuovo dato
`e inserito nel (sotto)albero a sinistra. Nel caso contrario, ossia valore maggiore, viene
valutata expr3 ed il nuovo dato `e inserito nel (sotto)albero a destra.
Lespressione
&(node)>right

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

yaC-Primer: Alberi binari

(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 ;
}

Funzione b tree size()


Lalbero binario non contiene informazioni sul numero di nodi dellalbero, di conseguenza la funzione utilizza la funzione privata del modulo size b tree() che conta
gli elementi dellalbero. La scopo della funzione size b tree() `e limitato al modulo
di conseguenza questa viene dichiarata nel modulo b tree.c, e non nel file di header
b tree.h, in classe di memorizzazione static.
static void size_b_tree ( node_t top , int cnt ) ;

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

yaC-Primer: Alberi binari

(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

yaC-Primer: Alberi binari

(Rev. 2.0.3)

printf ( "\ nTree Size: %d \n" , b_tree_size ( tree ) ) ;


printf ( "\n\ tTree\n\n" ) ;
b_tree_print ( tree ) ;
return 0 ;
}
/* ============================================================== */
/*
Funzioni
*/
/* ============================================================== */
void init_darray ( double a , int n )
{
int i ;
srand ( 1 2 3 4 5 6 7 ) ;
for ( i = 0 ; i < n ; ++i ) {
a [ i ] = 1 . 0 rand ( ) / ( RAND_MAX + 1 . 0 ) ;
}
return ;
}
void print_darray ( double a , int n )
{
int i ;
for ( i = 0 ; i < n ; ++i ) {
printf ( " array [%d] = %f\n" , i , a [ i ] ) ;
}
return ;
}


Il programma deve essere compilato come


$ cc testb_tree . c b_tree . c

ovvero come
$ cc c testb_tree . c
$ cc c b_tree . c
$ cc testb_tree . o b_tree . o

In entrambi i casi leseguibile si trova nel file a.out.


Quando il programma viene eseguito si ottiene
Dimensione array [< 1 exit ] : 10
Array

529

yaC-Primer: Alberi binari

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

numeri aleatori con questa propriet`


a sono chiamati identical independent distributed, o semplicemente iid,
random numbers.

531

yaC-Primer: Congruenze Lineari

(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.

5.2. Metodo delle Congruenze Lineari

(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

yaC-Primer: Congruenze Lineari

(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

La sequenza viene iniziata inizializzando il generatore attraverso la scelta di un seme


(seed) che fissa il valore del primo numero della sequenza I0 . Una volta fissato I0 le successive
chiamate del generatore produrranno la sequenza I1 , I2 , I3 , ...
La sequenza di numeri generata in questo modo `e periodica e si ripete con un periodo non
pi`
u grande di m. Il valore iniziale I0 fissa il punto di partenza lungo il ciclo. La lunghezza
del periodo dipende dalla scelta dei parametri a, c, m ed, eventualmente, anche da I0 perch`e
scelte diverse del punto di partenza possono generare sequenze cicliche diverse con periodi
` possibile mostrare sotto quali condizioni
non necessariamente uguali, anche se minori di m. E
la scelta dei parametri a, c, m produce sequenze di periodo massimo m. In questo caso la
scelta del valore iniziale I0 `e chiaramente irrilevante perch`e si ha un solo ciclo.
I generatori basati sulle congruenze lineari sono molto semplici da scrivere, come mostra il
seguente modulo randu.c.
Header: randu.h


/* ***************************************************************

- Definizioni per randu.c


- Funzioni :
void
srandu ( unsigned int seed );
int
randu(void );
double drandu (void );
- Descrizione :

533

yaC-Primer: Congruenze Lineari

(Rev. 2.0.1)

srandu () -> Inizializza il generatore


randu (); -> Numero intero uniforme tra 0 e RANDU_MOD -1
drandu (); -> Numero double uniforme in [0 ,1)
- $Id: randu.h v 1.1 23.10.04 AC
**************************************************************** */
extern void
srandu ( unsigned int seed ) ;
extern int
randu ( void ) ;
extern double drandu ( void ) ;


Modulo: randu.c


/* ***************************************************************




- Descrizione : Generatore Numeri Uniformi


Congruenze lineari
- $Id: randu.c v 1.1 23.10.04 AC
**************************************************************** */
# define RANDU MULT 1103515245UL
# define RANDU INC 12345UL
# define RANDU MOD 32768UL
/*
* Variabile globale privata del modulo che contiene
* lultimo numero generato
*/
static unsigned long next =1;
extern int randu ( void )
{
next = ( next RANDU_MULT + RANDU_INC ) % RANDU_MOD ;
return next ;
}
extern double drandu ( void )
{
return ( randu ( ) / ( double ) RANDU_MOD ) ;
}
extern void srandu ( unsigned int seed )
{
next = seed ;
return ;
}
# undef RANDU MULT
# undef RAND INC


La sequenza viene inizializzata dalla funzione srandu() che inizializza il generatore fissando

534

yaC-Primer: Congruenze Lineari

(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

yaC-Primer: Congruenze Lineari

(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 ) ;

in modo da utilizzare i bits pi`


u significativi e non
j = 1 + ( randu ( ) % 1 0 ) ;

che invece usa quelli meno significativi.


Park e Miller hanno proposto un generatore di numeri random basato sulle congruenze lineari
con la scelta
a = 75 = 16807, m = 231 1 = 2147483647, c = 0
e mostrato che questo soddisfa dei requisiti minimi di bont`a per un generatore di numeri
random. Sebbene questo generatore, chiamato Minimal Standard, abbia passato i tests non
`e tuttavia un generatore perfetto.

5.2.1. rand(), srand(), RAND MAX


Lo Standard C fornisce il generatore di numeri random di sistema rand()
# include < s t d l i b . h>
int rand ( void ) ;
void srand ( unsigned int seed ) ;

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

yaC-Primer: Semplice generatore numeri Gaussiani

(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.

5.3. Semplice generatore di numeri Gaussiani

(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

yaC-Primer: Semplice generatore numeri Gaussiani

(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 :

SEED seme generatore random


N
numero di elementi nella somma
SAMP numero di numeri generati

- Output :

numeri gaussiani sullo stdout

- $Id: gen_gauss -1.c v 1.1 24.10.04 AC


**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
# include <math . h>
int main ( void )
{
int i , sam ;
double
y;
double norm ;
/* Inizializzazione generatore uniforme */
srand ( SEED ) ;
/* Normalizzazione numeri Gaussiani */
norm = 1 . 0 / pow ( ( double ) N , 0 . 5 ) ;
for ( sam = 0 ; sam < SAMP ; ++sam ) {
y = 0.;
for ( i = 0 ; i < N ; ++i ) {
y += 2 . 0 rand ( ) / ( RAND_MAX + 1 . 0 ) 1 . 0 ;
}
y = norm ;
fprintf ( stdout , "%f\n" , y ) ;
}
return 0 ;
}


Note su gen gauss-1.c


Il programma prende tre parametri definiti attraverso delle macros:
SEED := seme generatore dei numeri random con distribuzione uniforme;

538

yaC-Primer: Metodo dellInversione

(Rev. 2.0.1)

N := numero di elementi della somma;


SAMP := numero di numeri Gaussiani generati.
Le macros non sono definite nel file, per cui devono essere definite in compilazione
utilizzando la flag -D del compilatore:
$ cc DSEED =12345 DN=5 DSAMP=1000 gen_gauss 1.c lm

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.

5.4. Metodo dellInversione

(Rev. 2.0.1)

Se si vogliono generare numeri random con una distribuzione di probabilit`a sufficientemente


semplice si pu`o utilizzare il metodo dellinversione. Prima di illustrare il metodo ricordiamo
che si definisce variabile random una qualsiasi variabile x che assume valori random. I valori
assunti dalla variabile e la loro distribuzione definiscono la distribuzione di probabilit`a p(x)
della variabile random.
Il metodo dellinversione si basa sulla seguente propriet`a del cambio di variabili: se x `e una
variabile random con una distribuzione di probabilit`a p(x) allora la funzione y(x) `e a sua
volta una variabile random con distribuzione di probabilit`a p(y).
Se la funzione y(x) `e invertibile (a pezzi) le distribuzione di probabilit`a p(x) e p(y) soddisfano
la relazione
p(y) |dy| = p(x) |dx|
che esprime la conservazione della probabilit`a. Assumendo che dy/dx sia positiva in tutto
lintervallo di interesse possiamo scrivere2
p(y) = p(x)
2

dx
.
dy

se la funzione y(x) `e invertibile a pezzi vi sar`


a una relazione di questo tipo per ogni intervallo in cui la
funzione `e invertibile.

539

yaC-Primer: Metodo dellInversione

(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.

5.4.1. Distribuzione uniforme in [a, b]


Nel caso di una distribuzione di probabilit`a uniforme nellintervallo [a, b]:

ba
p(y) =

ayb
altrimenti

semplici calcoli forniscono


x = F (y) =

ya
ba

per cui invertendo la relazione si ha lusuale formula di trasformazione


y(x) = a + (b a) x
Osserviamo che per a = 0 e b = 1 questa relazione si riduce correttamente ad una identit`a.

540

yaC-Primer: Metodo dellInversione

(Rev. 2.0.1)

5.4.2. Distribuzione Esponenziale


Come secondo esempio consideriamo una distribuzione di probabilit`a esponenziale:

(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, ).

5.4.3. Distribuzione Gaussiana


Per generare numeri random y con una distribuzione Gaussiana
 2
1
x
p(y) =
exp
2
2
utilizzando il metodo dellinversione conviene considerare due numeri random indipendenti
y1 e y2 entrambi con distribuzione Gaussiana a media nulla e varianza uguale ad 1. Generalizzando il precedente discorso al caso di due variabili indipendenti si vede facilmente che la
loro probabilit`a cumulativa congiunta `e
Z y2
Z y1
1
2
2
F (y1 , y2 ) = F (y1 ) F (y2 ) =
dy1
dy2 e(y1 +y2 )/2 .
2

Lintegrale pu`o essere fatto facilmente passando alle coordinate polari:


y1 = R cos ,

y2 = R sin .

Ponendo inoltre F (y1 ) = x1 e F (y2 ) = x2 otteniamo


Z Z
1
R2 /2
/
x1 x2 =
d
dR R eR 2 =
e
.
2 0
2
R
Siccome sia (x1 , x2 ) che (R, ) sono indipendenti possiamo porre:
x1 = eR

2 /2

x2 =

ottenendo cos` la formula di trasformazione


p
y1 =
2 ln x1 cos(2 x2 ),
p
y2 =
2 ln x1 sin(2 x2 )
che permette di passare da due variabili random indipendenti x1 e x2 con distribuzione uniforme in [0, 1] a due variabili random indipendenti y1 e y2 con distribuzione Gaussiana a valore
medio nullo e varianza unitaria. Questo metodo `e chiamato metodo di Box-Muller.

541

yaC-Primer: Monte Carlo Metropolis

(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.

5.5. Metodo di Metropolis

(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

yaC-Primer: Monte Carlo Metropolis

(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

T (x0 x1 ) T (x1 x2 ) T (xN 1 x)

x1 ,x2 ,...,xN 1

Lultima somma pu`o essere riscritta come

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

in quanto lespressione tra parentesi non `e altro che p(xN 1 = x0 ; N 1).


Questa relazione implica che se nel limite di N molto grandi la distribuzione p(x; N ) converge
ad una distribuzione asintotica p(x; ) allora
X
p(x0 ; ) T (x0 x).
p(x; ) =
x0

per ciascun nuovo numero generato avr`a sar`a un numero random con distribuzione di probabilit`a p(x; ).

543

yaC-Primer: Monte Carlo Metropolis

(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),

consideriamo la variazione di p(x; N ) nel passare da una sequenza di lunghezza N ad una di


lunghezza N +1. Utilizzando lespressione di p(x; N ) ottenuta precedentemente e la condizione
di normalizzazione delle T (x x0 ) la variazione pu`o essere scritta come:
X
X
p(x; N + 1) p(x, N ) =
p(x0 ; N ) T (x0 x) p(x, N )
T (x x0 )
x0

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

yaC-Primer: Monte Carlo Metropolis

(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


/* ***************************************************************

- Definizioni per metropolis .c

545

yaC-Primer: Monte Carlo Metropolis

(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 ) ;

/* Generatore numeri uniformi */

int metropolis ( double x , double dx , double ( prob ) ( double x ) )


{
double w , y ;
/* Punto di prova
*/
y = x + ( 2 . 0 get_randu ( ) 1 . 0 ) dx ;
/* Rapporto probabilit a */
w = prob ( y ) / prob ( x ) ;
/* Controllo se accettare o no */
if ( w >= 1 | | w > get_randu ( ) ) {
x = y ;
return 1 ;
}

546

yaC-Primer: Monte Carlo Metropolis

(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 ) ) ;
}


La funzione principale del modulo `e


int metropolis ( double x , double dx , double ( prob ) ( double x ) )

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


/* ***************************************************************

- Descrizione : test per metropolis .c

547

yaC-Primer: Monte Carlo Metropolis

(Rev. 2.0.2)

- $Id: test - metropolis .c v 1.0 24.10.04 AC


**************************************************************** */
# include < s t d l i b . h>
# include <math . h>
# include <s t d i o . h>
/*
* Usa modulo metropolis .c
*/
# include " metropolis .h"
# define SEED
# define DX
# define OUTF

12345
2.5
"data.dat"

/* Distribuzione asintotica p(x) */


double gauss ( double x ) ;
int main ( int argc ,
{
int
in ;
int n_number ;
int
n_bad ;
int
acc ;
double
x;
FILE
fp ;

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 ] ) ;

/* Valore iniziale x_0 */

fp = fopen ( OUTF , "w" ) ;


if ( fp == NULL ) {
fprintf ( stderr , "Open file failure \n" ) ;
return 1 ;
}
init_metropolis ( SEED ) ;
/* La prima parte della sequenza viene buttata */
acc = 0 ;
for ( in = 0 ; in < n_bad ; ++in ) {
acc += metropolis(&x , DX , gauss ) ;
}
printf ( " Accettazione : %f\n" , ( double ) acc / ( double ) n_number ) ;

548

yaC-Primer: Monte Carlo Metropolis

(Rev. 2.0.2)

/* Assumendo che p(x,N) sia piu o meno uguale a p(x)


* generiamo i numeri buoni
*/
acc = 0 ;
for ( in = 0 ; in < n_number ; ++in ) {
acc += metropolis(&x , DX , gauss ) ;
fprintf ( fp , "%f\n" , x ) ;
}
printf ( " Accettazione : %f\n" , ( double ) acc / ( double ) n_number ) ;
return 0 ;
}
double gauss ( double x )
{
double c ;
c = 0.5 x x ;
return exp ( c ) ;

/* Metropolis usa rapporto */


/* Non serve normalizzare */

}


Il programma prende i valori di input dalla linea di comando e utilizza le funzioni


# include < s t d l i b . h>
int atoi ( const char nptr ) ;
double atof ( const char nptr ) ;

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

Utilizzare per p(x) una distribuzione Gaussiana ed una esponenziale.


3. Un modo per ottenere una stima dellerrore fatto nel calcolo delle medie utilizzando
la funzione p(x; N ) ottenuta con il Monte Carlo `e quello di ripetere M volte il calcolo
della p(x, M ) utilizzando sequenze diverse. Se indichiamo con fm il valore della media

549

yaC-Primer: Monte Carlo Metropolis

(Rev. 2.0.2)

ottenuto utilizzando lm-esima sequenza, la stima dellerrore `e data dalla deviazione


standard:
!2
M
M
X
X
1
1
2
2
M
=
fm

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

yaC-Primer: Random Walk 1d

(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)

Ne segue che che P (x, N ) deve soddisfare lequazione:


P (x, N ) = p+ P (x 1, N 1) + p P (x + 1, N 1)
1
1
=
P (x 1, N 1) + P (x + 1, N 1).
2
2

(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 ) =

p(x0 ; N 1) T (x0 x).

x0

di un processo di Markov con x0 = x 1.


Lequazione per la P (x, N ) `e equazione lineare alle differenze finite a coefficienti costanti che
pu`o essere risolta facilmente ponendo
P (x, N ) = eix W (, N ) + eix W (, N )
dove `e un numero qualsiasi in [0, 2] e W (, N ) una funzione, eventualmente complessa,
da determinare. Il secondo termine, complesso coniugato del primo, `e stato aggiunto perch`e
la probabilit`a P (x, N ) `e una funzione reale. Tuttavia essendo lequazione per P (x, N ) lineare
una qualsiasi combinazione lineare delle soluzioni `e ancora una soluzione dellequazione per
cui possiamo utilizzare semplicemente
P (x, N ) = eix W (, N )

552

yaC-Primer: Random Walk 1d

(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)

ossia che ogni N il punto si trovi da qualche parte lungo la retta.


Il valore di W (, 0) `e determinato dalla condizione iniziale per N = 0, che nel nostro caso `e:
Z 2
1
P (x, 0) =
d eix W (, 0) = x,0 .
N 0
Ricordando che eix = cos(x) + i sin(x) e che
Z 2
Z 2
d cos(x) =
d sin(x) = 0,
0

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

yaC-Primer: Random Walk 1d

(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

5/4 3/2 7/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

dove  `e un numero piccolo da fissare a seconda del valore di N . Scrivendo


(cos )N = exp[N ln(cos )]
si vede chiaramente che per valori di al di fuori di questi intervalli lintegrando `e esponenzialmente depresso cosicch`e lerrore commesso trascurando questi valori `e esponenzialmente
piccolo in N .
Consideriamo separatamente i tre integrali.
'0
Scegliendo  sia sufficientemente piccolo (  1) nellintervallo [0, ] possiamo scrivere
cos ' 1

554

2
2
' e /2
2

yaC-Primer: Random Walk 1d

(Rev. 2.0.4)

come si vede facilmente dallo sviluppo di Taylor, cosicch`e




Z 
Z 
N 2
ix
N
d exp ix
.
d e (cos ) '
2
0
0
Lintegrando dellintegrale
a destra `e una funzione il cui modulo decresce esponenzialmente allaumentare di N dal valore 1 per = 0 al valore exp(N 2 /2) per = .
Di conseguenza se scegliamo  tale che
N 2  1 N 1/2    1
possiamo estendere lestremo superiore dellintegrazione a + in quando lerrore commesso `e esponenzialmente piccolo in N , e quindi trascurabile per N  1. Si ha cos`


Z 
Z
N 2
d eix (cos )N '
d exp ix
,
N  1.
(6.4)
2
0
0
Nelle due figure seguenti `e mostrato con la linea intera il grafico di (cos )6 e con la
linea tratteggiata quello di exp(6 2 /2) nellintervallo [0, /2].
1

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

yaC-Primer: Random Walk 1d

(Rev. 2.0.4)

La seconda uguaglianza `e stata ottenuta cambiando variabile di integrazione da ad


+ 2. Il fattore esponenziale scompare poich`e ei2x = 1 per ogni x intero. Come nel
caso ' 0, lultima uguaglianza `e valida per N 2  1, ovvero N 1/2    1.
'
Procedendo come nei due casi precedenti nellintervallo [ , + ] per   1 possiamo
scrivere
( )2
2
cos ' 1 +
' e() /2
2
per cui


Z +
Z +
N ( )2
d eix (cos )N ' (1)N
d exp ix
2




Z 
N 2
N ix
d exp ix
= (1) e
2



Z
2
N
d exp ix
,
N  1,
(6.6)
'
2

La seconda uguaglianza segue dal cambio di variabile , mentre il fattore


esponenziale scompare poich`e eix = (1)x per ogni x intero ed (1)x+N = 1 in quanto
avendo x ed N la stessa parit`a x + N `e un numero pari. Infine lultima uguaglianza `e
valida per N 2  1 ovvero N 1/2    1.
Confrontando i risultati ottenuti si vede che la somma del contributo dellintorno di = 0
(6.4) ed e di quello dellintorno di = 2 (6.5) `e uguale al contributo dellintorno di =
(6.6), per cui la somma dei tre integrali `e:


Z
2
N 2
P (x, N ) =
d exp ix
,
N  1.
2
2
Lintegrale pu`o essere facilmente valutato trasformandolo in un integrale Gaussiano completando il quadrato nellesponente ed utilizzando luguaglianza


Z
x2
dx exp 2 = 2 2
2

Ricordando infine che i2 = 1 si ottiene il risultato finale




2
x2
P (x, N ) =
exp
,
2N
2N

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

yaC-Primer: Random Walk Discreto 1d

(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.

6.2. Random Walk Discreto Simmetrico in una Dimensione

(Rev. 2.0.5)

La simulazione numerica di un Random Walk Discreto `e piuttosto semplice. Il seguente


programma ne illustra la procedura simulando un Random Walk discreto simmetrico in una
dimensione (p = p+ = 1/2) per calcolare probabilit`a P (x, N ) che un punto che parta
dallorigine x = 0 e che si muova lungo una retta con un Random Walk discreto simmetrico
si trovi dopo N passi nella posizione x = 0, 1, 2, . . . della retta.
La probabilit`a viene stimata calcolando la frequenza con cui differenti random walks che
partono dallorigine finiscono nella posizione x dopo N passi. A tal fine il programma genera
n walk random walks differenti di N = n step passi, tutti originati dalla posizione x = 0, e
calcola
numero cammini in x dopo N passi
P (x, N ) :=
,
N x N
n walk
Contemporaneamente il programma calcola i primi due momenti hxi ed hx2 i di P (x, n) in
funzione del numero n di passi fatti:
k

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

dove xn (i) `e la posizione raggiunta dalli-esimo walk dopo n passi.


Programma: rwd 1d.c


/* ***************************************************************

- 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

yaC-Primer: Random Walk Discreto 1d

(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 ] ;

/*
/*
/*
/*
/*
/*
/*
/*

passi del walk


walk
N massimo steps dei walks
Numero di walks
posizione x sulla retta
probabilita P(x,N)
seme generatore random
<x> e <x^2>

/* output files
/* input buffer

/* Inizializzazione generatore numeri random */


seed = 1 2 3 4 5 ;
srand ( seed ) ;

558

*/
*/
*/
*/
*/
*/
*/
*/

*/
*/

yaC-Primer: Random Walk Discreto 1d

(Rev. 2.0.5)

/* Parametri del run */


printf ( "\n Lunghezza massima walk? " ) ;
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" ,& n_step ) ;
printf ( "\n Quanti walks?
");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" ,& n_walk ) ;
pf_pr = open_file ( FILE_PR , "w" ) ;
printf ( "\n Probabilita su file: %s\n" , FILE_PR ) ;
pf_x2 = open_file ( FILE_ME , "w" ) ;
printf ( " Medie su file
: %s\n\n" , FILE_ME ) ;
/* alloca ed azzera gli array */
prob = ivect ( 2 n_step + 1 ) + n_step ;
for ( pos = n_step ; pos <= n_step ; ++pos ) {
prob [ pos ] = 0 ;
}
x1
= dvect ( n_step + 1 ) ;
x2
= dvect ( n_step + 1 ) ;
for ( step = 0 ; step <= n_step ; ++step ) {
x1 [ step ] = 0 . 0 ;
x2 [ step ] = 0 . 0 ;
}
/* Partenza dei walks */
for ( walk = 0 ; walk < n_walk ; ++walk ) {
/* Posizione di partenza del walk */
step
= 0;
pos
= 0;
x1 [ step ] += pos ;
x2 [ step ] += ( pos pos ) ;
for ( step = 1 ; step <= n_step ; ++step ) {
r
= rand ( ) / ( RAND_MAX + 1 . 0 ) ;
pos
+= ( r < 0 . 5 ? 1 : 1 ) ;
/* indietro se r < 0.5 */
x1 [ step ] += pos ;
x2 [ step ] += ( pos pos ) ;
}
/* numero di walks di n_step passi in pos */
++prob [ pos ] ;
}
/* ******************** */
/* Calcolo statistica */
/* ******************** */
norm = 1 . 0 / ( double ) ( n_walk ) ;

559

yaC-Primer: Random Walk Discreto 1d

(Rev. 2.0.5)

/* Probabilita P(pos , n_step ) */


for ( pos = n_step ; pos <= n_step ; ++pos ) {
fprintf ( pf_pr , "%d\t%f\n" ,
pos ,
( double ) prob [ pos ] norm ) ;
}
/* Momenti */
for ( step = 0 ; step <= n_step ; ++step ) {
fprintf ( pf_x2 , "%d\t%f\t%f\n" ,
step ,
( double ) x1 [ step ] norm ,
( double ) x2 [ step ] norm ) ;
}
return 0 ;
}
/* ---* 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 ;
}
/* ---* dvect ()
*/
double dvect ( int n )
{
double v ;
v = ( double ) malloc ( n sizeof ( double ) ) ;
if ( v == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory \n" ) ;
exit ( EXIT_FAILURE ) ;
}
return v ;
}
/* ---* open_file ()
*
*/

560

yaC-Primer: Random Walk Discreto 1d

(Rev. 2.0.5)

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 ;
}


Note su rwd 1d.c


prob = ivect ( 2 n_step + 1 ) + n_step ;
Il programma calcola la probabilit`a P (x, N ) per N = n step di conseguenza, siccome la posizione x varia da -n step a +n step, ci serve unarray di dimensione
2 * n step + 1. Larray `e creato utilizzando la funzione
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 ;
}

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

yaC-Primer: Random Walk Discreto 1d


x1

(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 ) ;
}

Questa `e la parte principale della simulazione. Si estrae un numero random r con


distribuzione uniforme tra [0, 1) e se questo `e minore di 0.5 il punto si sposta a sinistra
altrimenti viene spostato a destra. Una volta determinata la nuova posizione pos si
accumula sugli elementi di indice step degli arrays x1 ed x2 il primo e secondo momento
della posizione dopo step passi.
for ( walk = 0 ; walk < n_walk ; ++walk ) {
/* Posizione di partenza del walk */
step
= 0;
pos
= 0;
x1 [ step ] += pos ;
x2 [ step ] += ( pos pos ) ;
for ( step = 1 ; step <= n_step ; ++step ) {

562

yaC-Primer: Random Walk Discreto 1d

(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.

6.3. Random Walk Discreto Simmetrico in una Dimensione per


N  1 (Rev. 2.0.4)
Il seguente programma confronta lespressione esatta della probabilit`a P (x, N ) di trovare un
punto che effettua un Random Walk discreto simmetrico in una dimensione nella posizione
x = 0, 1, 2, . . . dopo N passi sapendo che il punto comincia a muoversi dallorigine x = 0:
1
P (x, N ) =
2

d cos(x) (cos )N

563

yaC-Primer: Random Walk Discreto 1d

con la forma asintotica:

(Rev. 2.0.4)



x2
2
exp
P (x, N ) =
,
2N
2N

valida per grandi valori di N .


Il programma usa il modulo integ.c per calcolare lintegrale con il metodo dei trapezi.
La parte immaginaria dellintegrale viene calcolata per controllare la precisione dellintegrazione
numerica.
Programma: rwd 1d-teo.c


/* ***************************************************************
- 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

- $Id: rwd_1d -teo.c v 1.2 30.10.04 AC


**************************************************************** */
# include <s t d i o . h>
# include <math . h>
# include < s t d l i b . h>
/*
* modulo integ.c
*/
# include " integ.h"
/* ============================================================== */
/*
Macros / Parametri
*/
/* ============================================================== */
# define PI
3.14159265358979
/* pi - greco
*/
# define N DIV
10000
/* # intervalli integ */
# define FILE PR " walk_teo .dat"
/* File output
*/

564

yaC-Primer: Random Walk Discreto 1d

(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

yaC-Primer: Random Walk Discreto 1d

(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 ) ) ;
}


Note su rwd 1d-teo.c


struct wlk {
int
x;
int steps ;
} Walk ;

/* posizione walk
*/
/* # passi del walk */

Il programma utilizza la funzione integ trap() il cui prototipo `e


double integ_trap ( double a , double b , int n ,
double ( func ) ( double ) ) ;

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

yaC-Primer: Random Walk Discreto 2d

(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.

6.4. Random Walk Discreto in due Dimensioni in un dominio finito


(Rev. 2.0.4)

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

yaC-Primer: Random Walk Discreto 2d

(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

yaC-Primer: Random Walk Discreto 2d

(Rev. 2.0.4)

quadrato di (2L+1) x (2L+1) siti.


Condizioni al bordo: free , reflecting , periodic
Calcola : <dx >, <dx^2>, <dy > e <dy^2> con
dx = x(n) - x(0) e dy = y(n) - y(0) ,
in funzione del numero di passi fatti.
- Input :
Numero massimo di passi per walk
Numero di walks per la statistica
- Output :
FILE_ME -> n <dx > <dy > <dx^2> <dy^2> (frac walks)
FILE_CF -> walk x y
- Parametri :
L:
N_CF:
FILE_ME :
FILE_CF :

Reticolo quadrato [-L,L] x [-L,L]


# walks da scrivere su FILE_CF
File output medie
File con coordinate walks

- $Id: rwd_2d .c v 2.1 08.11.04 AC


**************************************************************** */
# include <s t d i o . h>
# include < s t d l i b . h>
# include <math . h>
/* ============================================================== */
/*
Macros / Parametri
*/
/* ============================================================== */
# define L
10
# define N CF
4
# define FILE ME " walk_me .dat"
# define FILE CF " walk_cf .dat"
/* ============================================================== */
/*
Prototipi
*/
/* ============================================================== */
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 ) ;
int
ivect ( int n ) ;
double dvect ( int n ) ;
FILE open_file ( const char path , const char mode ) ;
int main ( void )
{
int
int
int
int

step ;
n_step ;
walk ;
n_walk ;

/*
/*
/*
/*

numero
valore
indice
numero

di passi N del walk


massimo N
del walk
di walks

*/
*/
*/
*/

569

yaC-Primer: Random Walk Discreto 2d

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)

/*
/*
/*
/*
/*
/*
/*
/*
/*
/*

posizione sul reticolo


posizione iniziale
contatore walk di step passi
prob right
prob left
prob up
prob down = 1 - u - r -l
<dx > e <dx^2> versus step
<dy > e <dy^2> versus step
Tavole primi vicini

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" ) ;

*/
*/
*/
*/
*/
*/
*/
*/
*/
*/

yaC-Primer: Random Walk Discreto 2d

#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_CF , FILE_CF ) ;

pf_me = fopen ( FILE_ME , "w" ) ;


printf ( " Medie 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

yaC-Primer: Random Walk Discreto 2d

(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

yaC-Primer: Random Walk Discreto 2d

(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

yaC-Primer: Random Walk Discreto 2d

(Rev. 2.0.4)

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 ;
}
/* ---* dvect ()
*/
double dvect ( int n )
{
double v ;
v = ( double ) malloc ( n sizeof ( double ) ) ;
if ( v == NULL ) {
fprintf ( stderr , "\n Cannot allocate memory \n" ) ;
exit ( EXIT_FAILURE ) ;
}
return v ;
}
/* ---* 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 ;
}


Note su rwd 2d.c


Condizioni al bordo
A causa della dimensione finita del dominio `e possibile che il Random Walk raggiunga il bordo
del dominio, specialmente se il numero di passi del walk `e maggiore delle dimensioni lineari

574

yaC-Primer: Random Walk Discreto 2d

(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 ] ;

sposta a destra il punto che si trova nella posizione di coordinata x =pos x.


La geometria del sistema `e simmetrica per lo scambio di x con y per cui gli arrays ip1 e im1
possono essere utilizzati sia per gli spostamenti orizzontali che verticali, ad esempio
pos_y = im1 [ pos_y ] ;

sposta in basso il punto nella posizione di coordinata y =pos y.


Il Random Walk avviene su un quadrato [L, L] [L, L] per cui entrambi gli arrays devono
essere di dimensione 2L + 1. Inoltre siccome entrambe le coordinate x e y variano tra L ed
L inclusi i due puntatori ip1 e im1 agli arrays sono spostati a destra di L posizioni
ip1 = ivect ( 2 L + 1 ) + L ;
im1 = ivect ( 2 L + 1 ) + L ;

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

yaC-Primer: Random Walk Discreto 2d

(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 ;

per ogni valore di i minore di i max e


ip1 [ i_max ] = i_max 1 ;

Analogamente
im1 [ i ] = i 1 ;

per ogni valore di i maggiore di i min e


im1 [ i_min ] = i_min + 1 ;

Funzione set peri bc()


Questa funzione fissa le condizioni riflettenti al bordo e differisce dalla precedente solo per i
valori al bordo che adesso sono
ip1 [ i_max ] = i_min ;
im1 [ i_min ] = i_max ;

576

yaC-Primer: Random Walk Discreto 2d

(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 ;

di conseguenza il controllo viene fatto sequenzialmente a partire dallintervallo pi`


u a sinistra:
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 ;
}

Una volta determinato lintervallo si effettua la mossa corrispondente utilizzando le tavole


dei vicini. Se si utilizzano condizioni libere al bordo il walk pu`o uscire dal dominio per cui
dopo aver fatto la mossa si controlla se il walk `e uscito dominio nel qual caso viene fermato
interrompendo il ciclo. Questo si ottiene con listruzione break che interrompe il ciclo for e
quindi levoluzione del walk. Nel caso di condizioni riflettenti o periodiche il walk non pu`o
uscire dal dominio per cui il controllo `e ineffettivo.

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

yaC-Primer: Random Walk Discreto 2d

=
=
=
=

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

yaC-Primer: Random Walk Discreto 2d

(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

yaC-Primer: Random Walk Discreto 2d

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.

Automi Cellulari Unidimensionali


Lesempio pi`
u semplice di automa cellulare sono gli automi cellulari Booleani Unidimensionali
in cui i vicini di un sito sono il sito immediatamente a sinistra e quello immediatamente a

581

yaC-Primer: Automi Cellulari

(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

yaC-Primer: Automi Cellulari

(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

/* numero sito automa


*/
/* puntatore al primo sito */

( 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 ) ;

int main ( void )


{
int
i;
int
n_step ;
int
step ;
cellular_t automata ;
cell_t
rule [ 8 ] ;
cell_t
rule_code ;

/*
/*
/*
/*
/*
/*

indice sito automa


# intervalli temporali
contatore passi temp
automa cellulare
tavola regola
codice numerico regola

*/
*/
*/
*/
*/
*/

583

yaC-Primer: Automi Cellulari

(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 ) ;

: ");

printf ( " Codice numerico regola


fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%hu" , &rule_code ) ;

: ");

printf ( " Passi temporali


fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , &n_step ) ;

: ");

printf ( "Seed condizione iniziale (== 0 fissa) : " ) ;


fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lu" , &seed ) ;
/* Tavola della regola */
table_rule ( rule , rule_code ) ;
/* Variabili sul reticolo */
automata>site = create_site ( automata>size ) ;
/* Inizializzazione Automa */
init_state ( automata , seed ) ;
/* File di output */
fp = fopen ( OUT_F , "w" ) ;
if ( fp == NULL ) {
fprintf ( stderr , "File open failure \n" ) ;
return 1 ;
}
/* Configurazione iniziale */
for ( i = 0 ; i < automata>size ; ++i ) {
if ( automata>site [ i ] ) fprintf ( fp , "%d %d\n" , 0 , i + 1 ) ;
}
/* Evoluzione */
for ( step = 1 ; step <= n_step ; ++step ) {
update_state ( automata , rule ) ;

584

yaC-Primer: Automi Cellulari

(Rev. 2.0.3)

for ( i = 0 ; i < automata>size ; ++i ) {


if ( automata>site [ i ] ) fprintf ( fp , "%d %d\n" , step , i +1);
}
}
fclose ( fp ) ;
return 0 ;
}
/* ---* create_site ()
*
*/
cell_t create_site ( int n )
{
cell_t c ;
c = ( cell_t ) malloc ( n sizeof ( cell_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( 1 ) ;
}
return c ;
}
/* ---* table_rule ()
*
*/
void table_rule ( cell_t rule , cell_t rule_code )
{
int
i;
char str [ 4 ] ;
/* stampa le configurazioni */
fprintf ( stdout , " tavola regola | " ) ;
for ( i = 7 ; i >= 0 ; i ) {
bit_to_str ( str , 3 , i ) ;
printf ( "%s " , str ) ;
}
printf ( "\n" ) ;
fprintf ( stdout , " codice
%3d | " , rule_code ) ;
/* Tavola */
for ( i = 0 ; i <= 7 ; ++i ) {
rule [ i ] = rule_code & 1 ;
rule_code >>= 1 ;
}
/* stampa la tavola */
for ( i = 7 ; i >= 0 ; i ) {

585

yaC-Primer: Automi Cellulari

printf ( " %1d


}
printf ( "\n" ) ;

(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

yaC-Primer: Automi Cellulari

(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

*/
*/
*/
*/

/* configurazione temporanea per evoluzione parallela */


new_site = create_site ( aut>size ) ;
/* Usiamo condizioni periodiche al bordo
/* Vicino a sinistra di 0 e N -1
/* Vicino a destra di N -1 e 0
for ( i = 0 ; i < aut>size ; ++i ) {
im1 = ( i1 + aut>size ) % aut>size ;
ip1 = ( i+1)
% aut>size ;
conf_index =
4 aut>site [ im1 ] + 2
+
aut>site [ ip1 ] ;
new_site [ i ] = rule [ conf_index ] ;
}

*/
*/
*/

aut>site [ i ]

for ( i = 0 ; i < aut>size ; ++i ) {


aut>site [ i ] = new_site [ i ] ;
}

587

yaC-Primer: Automi Cellulari

(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

yaC-Primer: Automi Cellulari

(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 ;
}

Se il valore di seed `e 0 la configurazione iniziale deve essere data specificando la posizione


delle celle con valore 1.
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 {

589

yaC-Primer: Automi Cellulari

(Rev. 2.0.3)

printf ( "Sito non valido , gia scelto \n" ) ;


}
}
}

Le celle vengono inserite utilizzando un ciclo infinito


while ( 1 ) {
...
}

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" ) ;
}

Evoluzione: funzione update state()


Lo stato delle cellette deve essere aggiornato simultaneamente, in altre parole il nuovo valore
delle cellette al tempo t + 1 deve essere determinato usando per tutte le cellette il valore al
tempo t. Questo tipo di evoluzione viene chiamata sincrona o parallela.
Il modo pi`
u semplice di realizzare una evoluzione sincrona `e utilizzando un array temporaneo
su cui viene memorizzato il nuovo valore delle cellette generato a partire dai valori vecchi. La
funzione update state() usa a questo scopo larray temporaneo new site.
cell_t new_site ;
...
new_site = create_site ( aut>size ) ;

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 ] ;
}

e lo spazio utilizzato dallarray temporaneo restituito al sistema


free ( new_site ) ;

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

yaC-Primer: Game of Life

conf_index

=
+

(Rev. 2.0.3)

4 aut>site [ im1 ] + 2 aut>site [ i ]


aut>site [ ip1 ] ;

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
.

......

Nel programma questo `e realizzato con le istruzioni


im1 = ( i1 + aut>size ) % aut>size ;
ip1 = ( i+1)
% aut>size ;

come `e facile convincersi considerando, ad esempio, i casi i=0, i=1 e i=aut->size-1.


Questo non `e lunico modo di realizzare le condizioni periodiche al bordo. Ad esempio si
potrebbe eseguire il ciclo partendo dalla seconda cella sino fino alla penultima e scrivere
esplicitamente la regola per la prima e lultima cella. Questo metodo `e chiaramente pi`
u
efficiente di quello che richiede le operazioni di modulo, ma ha lo svantaggio che se la regola
di evoluzione `e piuttosto complicata, non come in questo caso, `e pi`
u facile fare errori in quanto
questa va replicata tre volte: una volta per le celle a ciascun estremo ed una volta per tutte le
altre celle. Il numero di repliche inoltre aumenta con il numero di dimensioni del sistema,
per cui se sono tre in una dimensione saranno nove in due e cos` via. Un altro metodo `e
quello di utilizzare tavole dei vicini che per ogni sito contengano gli indici dei siti primi vicini,
come fatto ad esempio per il Random Walk in due dimensioni. Questo metodo `e piuttosto
flessibile e per questo viene utilizzato quando il sistema `e definito su geometrie particolari o
se il numero di vicini pu`o variare nel tempo come ad esempio nelle simulazioni di dimanica
molecolare.

591

yaC-Primer: Game of Life

(Rev. 2.0.3)

7.2. Automi Cellulari Booleani Bidimensionali: Game of Life

(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

Se 2 o 3 siti vicini uguali a 1


Altrimenti
Se 3 siti vicini uguali a 1
Altrimenti

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

yaC-Primer: Game of Life

(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

/* numero sito automa


*/
/* puntatore al primo sito */

( int n ) ;

593

yaC-Primer: Game of Life

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 ) ;

int main ( void )


{
int
i, j;
int
n_step ;
int
step ;
cellular_t automata ;
cell_t
rule [ 5 1 2 ] ;
unsigned long
seed ;
char
line [ 8 1 ] ;
FILE
fp ;

/*
/*
/*
/*
/*
/*
/*
/*

indici siti automa


# intervalli temporali
contatore passi temp
automa cellulare
tavola regola
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 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 ) ;

: ");

: ");

printf ( "Seed condizione iniziale (=< 0 fissa) : " ) ;


fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%lu" , &seed ) ;
/* Tavola della regola */
table_rule ( rule ) ;
/* Variabili sul reticolo */
automata>site = create_site ( automata>size ) ;
/* Inizializzazione Automa */
init_state ( automata , seed ) ;
/* File di output */
fp = fopen ( OUT_F , "w" ) ;
if ( fp == NULL ) {
fprintf ( stderr , "File open failure \n" ) ;
return 1 ;
}

594

*/
*/
*/
*/
*/
*/
*/
*/

yaC-Primer: Game of Life

(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

yaC-Primer: Game of Life

(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

*/

yaC-Primer: Game of Life

(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

yaC-Primer: Game of Life

(Rev. 2.0.3)

static int start = 1 ;


cell_t
new_site ;

/* flag start
*/
/* nuova configurazione aut */

/* Se start tavole vicini */


if ( start ) {
start
= 0;
im1
ip1

= ivect ( aut>size ) ;
= ivect ( aut>size ) ;

/* Usiamo condizioni periodiche al bordo */


/* Vicino a sinistra di 0 e N -1
*/
/* Vicino a destra di N -1 e 0
*/
for ( i = 0 ; i < aut>size ; ++i ) {
im1 [ i ] = ( i1 + aut>size ) % aut>size ;
ip1 [ i ] = ( i+1)
% aut>size ;
}
}
/* configurazione temporanea per evoluzione parallela */
new_site = create_site ( aut>size ) ;
/* Update configurazione */
for ( i = 0 ; i < aut>size ; ++i ) {
ip = ip1 [ i ] ;
im = im1 [ i ] ;
for ( j = 0 ; j < aut>size ; ++j ) {
jp = ip1 [ j ] ;
jm = im1 [ j ] ;
/* 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 ] ;
new_site [ i ] [ j ] = rule [ index ] ;
}
}
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 ;
}

598

yaC-Primer: Game of Life

(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 ;

/* numero sito automa


*/
/* puntatore al primo sito */

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

ed associando a ciascuna configurazione il valore dellindice index ottenuto interpretando il


valore Si = (0, 1) delli-esimo sito come il valore delli-esimo bit della rappresentazione binaria
del valore di index
index = S8 S7 S6 S5 S4 S3 S2 S2 S2 S1 S0
S8 28 + S7 27 + + S2 22 + S1 21 + S0
Ad esempio la configurazione con tutti i siti uguali a 1 viene identificata con il valore 511 di
index mentre quella con tutti i siti 0 con in valore 0.

599

yaC-Primer: Game of Life

(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

*/

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. La funzione `e lestensione al caso bidimensionale
della funzione init state() utilizzata per il caso unidimensionale.
Evoluzione: funzione update state()
Come nel caso unidimensionale levoluzione sincrona in cui lo stato di tutti i siti viene cambiato
simultaneamente, `e realizzata utilizzando un array temporaneo new site

600

yaC-Primer: Game of Life

(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 ] ;
}
}

e lo spazio utilizzato dallarray temporaneo restituito al sistema


free ( new_site ) ;

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 ] ;

Evoluzione senza Tavola della Regola


Lutilizzo della tavola della regola richiede sia memoria, larray rule, che un certo numero
di moltiplicazioni e somme. Queste possono essere ridotte, e quindi avere un programma pi`
u
efficiente, osservando che la regola del Game of Life `e equivalente a determinare per ogni sito

601

yaC-Primer: Game of Life

(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 ) ;

/* Usiamo condizioni periodiche al bordo */


/* Vicino a sinistra di 0 e N -1
*/
/* Vicino a destra di N -1 e 0
*/
for ( i = 0 ; i < aut>size ; ++i ) {
im1 [ i ] = ( i1 + aut>size ) % aut>size ;
ip1 [ i ] = ( i+1)
% aut>size ;
}
}
/* configurazione temporanea per evoluzione parallela */
new_site = create_site ( aut>size ) ;
/* Update configurazione */
for ( i = 0 ; i < aut>size ; ++i ) {
ip = ip1 [ i ] ;

602

*/
*/
*/
*/
*/
*/

yaC-Primer: Game of Life

(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

yaC-Primer: Game of Life

(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

Geometricamente questo vuol dire che lautoma cellulare vive su un toro.


` facile rendersi conto che le condizioni periodiche possono essere imposte utilizzando laritmeE
tica modulo L, dove L `e il numero di siti del reticolo per lato. Per limitare il numero di
operazioni nel programma si utilizza una tavola dei vicini costruita con i due arrays di interi
im1 e ip1 di dimensione aut->size = L i cui elementi di indice i contengono rispettivamente
la coordinata del sito prima e dopo il sito di coordinata i:
for ( i = 0 ; i < aut>size ; ++i ) {
im1 [ i ] = ( i1 + aut>size ) % aut>size ;
ip1 [ i ] = ( i+1)
% aut>size ;
}

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

*/

yaC-Primer: Game of Life

(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.

7.2.1. Animazione con gnuplot


` possibile animare il risultato della simulazione utilizzando gnuplot. Supponiamo che il
E
file life.dat generato dal programma game life.c contenga 200 configurazioni successive
per un reticolo di 20 20 siti.
Per prima cosa si deve creare un file, che potremmo chiamare looper.gpl, che contenga le
istruzioni per il ciclo:
File: looper.gpl


# File : looper . gpl
#
plot $0 u ( $$1 == time ? $$2 : 1 / 0 ) : 3
pause 1
time = time + 1
if ( time < $1 ) reread


Fatto questo si invoca gnuplot e si eseguono i seguenti comandi:


gnuplot>
gnuplot>
gnuplot>
gnuplot>
gnuplot>
gnuplot>

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

In questo modo le configurazioni al tempo t = 0, 1, . . . , 10 vengono mostrate in successione


ad intervalli di 1 secondo. Cambiando il valore di time e del terzo parametro dellistruzione
call `e possibile cambiare lintervallo di tempo visualizzato.

605

yaC-Primer: Game of Life

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

Tutti i clusters sono composti da un numero finito di siti pieni


Esiste uno spanning cluster

607

yaC-Primer: Percolazione

(Rev. 2.0.4)

Il valore pc `e chiamato soglia di percolazione. Quando il valore di p passa per la soglia


di percolazione pc le caratteristiche di connettivit`a del reticolo cambiano drasticamente, di
conseguenza la transizione di percolazione da uno stato senza spanning cluster ad uno con
spanning cluster `e un esempio di transizione di fase tra due fasi con caratteristiche diverse di
uno stesso sistema per un valore ben definito di un parametro continuo.
Molti problemi fisici possono essere ricondotti a problemi di percolazione, come ad esempio
lo studio della transizione metallo-isolante osservata in sistemi composti da una mistura di
un materiale isolante ed uno metallico. Siccome il metallo `e un conduttore allaumentare
della densit`a del componente metallico si osserva una transizione da una fase isolante, in cui
non vi `e uno spanning cluster di materiale conduttore, ad una conduttrice in cui invece il
materiale conduttore forma uno spanning cluster lungo cui passa la corrente. La lista degli
esempi e dei campi in cui la percolazione trova applicazione `e molto lunga ed esula dai nostri
scopi, tuttavia per mostrare limportanza della percolazione anche in altri campi citiamo lo
studio della propagazione delle epidemie nelle popolazioni ed del flusso del petrolio nelle rocce
porose, due problemi il cui interesse `e chiaramente non limitato alla fisica.

8.1.1. Semplice studio numerico della soglia di percolazione


Il metodo diretto di studiare numericamente la soglia di percolazione `e chiaramente quello di
assegnare ai siti di un reticolo di geometria data il valore pieno o vuoto con una probabilit`
a
fissata p e di determinare se esiste uno spanning cluster. Ripetendo lo studio per diversi valori
di p `e cos` possibile determinare il valore critico per cui lo spanning cluster appare. Questa
semplice procedura presenta, tuttavia, alcuni problemi.
Per definizione la soglia di percolazione pc `e definita come il valore della probabilit`a p per
cui appare per la prima volta uno spanning cluster in un reticolo infinito. Di conseguenza,
siccome su un calcolatore non `e chiaramente possibile simulare un reticolo infinito, lo studio
numerico della percolazione su un reticolo di geometria fissata fornir`a delle stime pc (L) il cui
valore dovr`a poi essere estrapolato in qualche modo ad L .
Questo `e solo uno, ed il pi`
u semplice, dei problemi poich`e a ben pensarci la definizione stessa
di pc (L) `e piuttosto ambigua poich`e per sistemi di dimensione lineare finita la probabilit`a che
esista uno spanning cluster `e finita per ogni valore di p. Tuttavia per valori piccoli di p questa
probabilit`a `e dellordine di pL e quindi va a zero rapidamente con le dimensioni lineari del
sistema cosicch`e per p sufficientemente piccolo ed L sufficientemente grande i clusters generati
numericamente saranno tutti di dimensione piccola rispetto ad L. Al contrario per valori di
p grandi esister`a sicuramente spanning cluster per cui `e possibile determinare numericamente
un valore pc (L) a cui si ha la transizione.
Se questo risolve, almeno a livello concettuale, la possibilit`a di determinare numericamente
un valore pc (L) lascia completamente insoluto il problema della definizione dello spanning
cluster su un reticolo di dimensione lineare finita L. Infatti questo pu`o essere definito come
il cluster che va da una parte allaltra del sistema lungo una direzione fissata, ad esempio
nel caso bidimensionale orizzontalmente, oppure come il cluster che unisce tutti i lati del
sistema e cos` via. Inoltre differenti configurazioni di siti pieni e vuoti generate con lo stesso
p possono avere clusters con propriet`a differenti e quindi il valore critico di p pu`o dipendere
dalla configurazione generata. Una possibilit`a `e quindi quella di definire pc (L) come la media

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)

- $Id: sp_cluster -rec.c v 1.4 08.11.04 AC


**************************************************************** */
# include < s t d l i b . h>
# include <s t d i o . h>
/* Macros */
# define L 40
/* Site state */
typedef enum { empty , full , seen } site_t ;
/* Prototipi */
void
init_sq ( site_t sq , double prob ) ;
int
percol ( site_t sq , int x , int y ) ;
site_t crea_sq ( int n ) ;
int main ( void )
{
int
x, y;
site_t
sq ;
int
n_touch ;
int
n_sample ;
int
sample ;
double
prob ;
unsigned int seed ;
char
line [ 8 1 ] ;

/*
/*
/*
/*
/*
/*
/*
/*

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)

if ( ( sq [ x ] [ y ] == full ) && percol ( sq , x , y ) ) {


++n_touch ;
break ;
}
}
}
fprintf ( stdout , "%f \t %f \n" , prob ,
( double ) n_touch / ( double ) sample ) ;
return 0 ;
}
/* ---* crea_sq ()
*/
site_t crea_sq ( int n )
{
site_t c ;
int
i;
c = ( site_t ) malloc ( n sizeof ( site_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( EXIT_FAILURE ) ;
}
for ( i =0; i < n ; ++i ) {
c [ i ] = ( site_t ) malloc ( n sizeof ( site_t ) ) ;
if ( c [ i ] == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( EXIT_FAILURE ) ;
}
}
return c ;
}
/* ---* init_sq ()
*/
void init_sq ( site_t sq , double prob )
{
int
x, y;
/* coordinate reticolo */
double
r;
/* numero random unif */
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 ;
}
}
return ;
}

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 ;

e larray viene definita utilizzando la funzione


site_t crea_sq ( int n )
{
site_t c ;
int
i;
c = ( site_t ) malloc ( n sizeof ( site_t ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( EXIT_FAILURE ) ;
}

612

yaC-Primer: Percolazione

(Rev. 2.0.4)

for ( i =0; i < n ; ++i ) {


c [ i ] = ( site_t ) malloc ( n sizeof ( site_t ) ) ;
if ( c [ i ] == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( EXIT_FAILURE ) ;
}
}
return c ;
}

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.

Identificazione dei Clusters: funzione percol()


Lidentificazione dello spanning cluster `e fatta dalla funzione percol() che ritorna 1 se il
cluster che parte dal sito pieno di coordinate (x, y) raggiunge un sito qualsiasi di coordinate
(L 1, y), o 0 altrimenti. La funzione pu`o essere scritta sia in forma ricorsiva che in forma
iterativa.

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;

Nel caso affermativo si interrompe la riscotruzione del cluster e la funzione ritorna il


valore 1. In caso contrario, ossia se i siti primi vicini non sono pieni o se nessuno dei
clusters che partono dai siti primi vicini pieni raggiunge il bordo destro, la funzione
ritorna il valore 0.
Siccome il valore dei siti pieni gi`a controllati viene modificato e lidentificazione del cluster si
interrompe solo se non vi sono pi`
u siti primi vicini pieni, ossia con il valore 1, o se il sito primo
vicino di destra `e pieno ed `e sul bordo destro, `e facile convincersi che il ciclo si interrompe
solo quando tutto il cluster `e stato identificato ovvero non appena questo raggiunge il lato
destro del reticolo.
Formulazione Iterativa
Come tutti gli algoritmi ricorsivi anche in questo caso `e possibile scrivere la funzione percol()
in forma iterativa. La seguente usa uno stack per memorizzare le coordinate dei siti pieni gi`
a
visitati da cui ripartire successivamente per costruire il cluster.

614

yaC-Primer: Percolazione

(Rev. 2.0.4)

Funzione: percol() formulazione iterativa


/* ---* 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 ;

/* stack punti cluster */

/* 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 ) ;

*/

/* Spostamento dx sui siti pieni:


sito vuoto
-> fine spostamento
sito sul bordo -> ritorna 1
Siti pieni sullo stack
*/
pnt . x = pnt_r . x + 1 ;
pnt . y = pnt_r . y ;
while ( pnt . x < L && ( sq [ pnt . x ] [ pnt . y ] == full ) ) {
sq [ pnt . x ] [ pnt . y ] = 2 ;
push ( pnt , stack ) ;
/* Se il sito e sul bordo svuota lo stack ed esci */
if ( pnt . x == xc ) {
while ( filled ( stack ) ) pop ( stack ) ;
return 1 ;
}
++pnt . x ;

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

yaC-Primer: Percolazione di Sito

(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 ;
}

perch`e il cluster ha raggiunto il bordo.


Per lo stack si pu`o utilizzare il modulo stack.c con dati
typedef struct {
int x ;
int y ;
} stack_data ;

modificando opportunamente lheader file stack.h.

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.

8.2. Soglia di Percolazione di Sito

(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

yaC-Primer: Percolazione di Sito

(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

yaC-Primer: Percolazione di Sito

(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

yaC-Primer: Percolazione di Sito

620

(Rev. 2.0)

9. Compressione delle Imagini


9.1. Compressione delle immagini: Iterated Function System

(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

yaC-Primer: Iterated Function System

(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

Esempio: Foglia di felce

W
W1
W2
W3
W4

a
0
0.20
0.15
0.85

Esempio: Sierpinsky carpet

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

yaC-Primer: Iterated Function System


W
W1
W2
W3
W4
W5
W6
W7
W8

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

Il seguente programma illustra la costruzione del Sierpinsky gasket con lIFS.


Programma: ifs.c


/* ***************************************************************

- Descrizione : Calcola lttrattore di un IFS


Usa yac_utils
- Input :

Numero di iterazioni

- Output :

Sul file lattrattore

- Parametri :

N : numero di elementi della IFS


OUT_F : file di output

- $Id: ifs.c v 1.1 13.11.03 AC


**************************************************************** */
# include < s t d l i b . h>
# include <s t d i o . h>
# include " yac_utils .h"
# define N
3
# define OUT F " attract .dat"
typedef struct {
double a ;
double b ;
} transf_t ;

/* trasformazione W */

void create_ifs ( transf_t ifs , double prb ) ;


int get_transf ( double prb ) ;
int main ( void )
{
int seed ;
int iter , n_iter ;
int ind ;
transf_t ifs ;
double
prb ;
double x , y ;

623

yaC-Primer: Iterated Function System

(Rev. 2.0.1)

double x_tmp , y_tmp ;


char line [ 8 1 ] ;
FILE fp ;
seed = 1 2 3 4 5 ;
printf ( " Numero iterazioni
: ");
fgets ( line , sizeof ( line ) , stdin ) ;
sscanf ( line , "%d" , &n_iter ) ;
ifs = ( transf_t ) malloc ( N sizeof ( transf_t ) ) ;
if ( ifs == NULL ) {
fprintf ( stderr , " Memory allocation failure \n" ) ;
exit ( 1 ) ;
}
prb = dvect ( N ) ;
create_ifs ( ifs , prb ) ;
fp = open_file ( OUT_F , "w" ) ;
/* Punto di partenza */
x = 1.0;
y = 1.0;
srand ( seed ) ;
for ( iter = 0 ; iter < n_iter ; ++iter ) {
ind
= get_transf ( prb ) ;
x_tmp = ifs [ ind ] . a [ 0 ] [ 0 ] x + ifs [ ind ] . a [ 0 ] [ 1 ] y + ifs [ ind ] . b [ 0 ] ;
y_tmp = ifs [ ind ] . a [ 1 ] [ 0 ] x + ifs [ ind ] . a [ 1 ] [ 1 ] y + ifs [ ind ] . b [ 1 ] ;
x
= x_tmp ;
y
= y_tmp ;
fprintf ( fp , "%f %f\n" , x , y ) ;
}
return ( 0 ) ;
}
/* ---* get_transf ((
*/
int get_transf ( double prb )
{
int i ;
static int start = 1 ;
static double p ;
double xxx ;
if ( start ) {
start = 0 ;
p
= dvect ( N ) ;
p [ 0 ] = prb [ 0 ] ;

624

yaC-Primer: Iterated Function System

(Rev. 2.0.1)

for ( i = 1 ; i < N ; ++i ) p [ i ] = p [ i 1] + prb [ i ] ;


}
xxx = ( double ) rand ( ) / ( RAND_MAX + 1 . 0 ) ;
i = 0;
while ( i < N && xxx > p [ i ] ) ++i ;
return ( i ) ;
}
/* ---* create_ifs ()
*/
void create_ifs ( transf_t ifs , double prb )
{
int i ;
for ( i = 0 ; i < N ; ++i ) {
ifs [ i ] . a = dmatr ( 2 , 2 ) ;
ifs [ i ] . b = dvect ( 2 ) ;
}
/* W_1 */
ifs [ 0 ] . a [ 0 ] [ 0 ]
ifs [ 0 ] . a [ 0 ] [ 1 ]
ifs [ 0 ] . a [ 1 ] [ 0 ]
ifs [ 0 ] . a [ 1 ] [ 1 ]
ifs [ 0 ] . b [ 0 ]
ifs [ 0 ] . b [ 1 ]
prb [ 0 ]

=
=
=
=
=
=
=

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

yaC-Primer: Iterated Function System

(Rev. 2.0.1)

Se si vogliono utilizzare altre IFS basta modificare la funzione create ifs().

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 :

su stdout tempo , energia meccanica


su files tempo , posizione e velocita dei pianeti .
formato :
tempo x y v_x v_y

- 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

- $Id: planets -rk.c v 1.1 09.10.03 AC


**************************************************************** */

627

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

# 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 P1 " plan1.dat"
/* out -file pianeta 1
*/
# define OUT P2 " plan2.dat"
/* out -file pianeta 2
*/
# define G
1.0
/* Costante di Gravitazione
*/
/* ============================================================== */
/*
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 ;

/* Massa del pianeta


*/
/* Coordinate del pianeta */
/* Velocita del pianeta
*/

/* ============================================================== */
/*
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 */

/* crea spazio per il pianeta */

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

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 ) ) ;
/* Integrazione con Runge - Kutta */
for ( tim = 1 ; tim < tim_max ; ++tim )
{
/* Un passo t -> t + dt */
rk4 ( dt , planet ) ;
/* scrivi sui files */
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 ) ) ;
}
fclose ( outf_1 ) ;
fclose ( outf_2 ) ;
return ( 0 ) ;
}
/*
/*
/*
/*
*
*
*

============================================================== */
Routines di definizione
*/
============================================================== */
---create_planet ()

Riserva lo spazio di memoria per un dato di tipo planet


*/
planet_ty create_planet ( int n )
{
planet_ty c ;
c = ( planet_ty ) malloc ( n sizeof ( planet_ty ) ) ;
if ( c == NULL ) {
fprintf ( stderr , " Cannot allocate space for planet \n\n" ) ;
exit ( 1 ) ;
}

629

yaC-Primer: Pianeti con Runge-Kutta

(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

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

if ( 1 fabs ( c_angle ) < MIN_COS ) {


fprintf ( stderr , "\n Collisione tra pianeti ! \n" ) ;
exit ( 9 ) ;
}
/* 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 ) ;
scanf ( "%d" , tm ) ;
return ;
}
# undef MIN COS

/* macro locale a questa funzione */

/* ---* 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

yaC-Primer: Pianeti con Runge-Kutta

(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

= 0 . 5 p [ 0 ] . mass ( p [ 0 ] . vel . x p [ 0 ] . vel . x +


p [ 0 ] . vel . y p [ 0 ] . vel . y ) ;

ene += 0 . 5 p [ 1 ] . mass ( p [ 1 ] . vel . x p [ 1 ] . vel . x +


p [ 1 ] . vel . y p [ 1 ] . vel . y ) ;
ene += (( double ) G ) p [ 0 ] . mass p [ 1 ] . mass / dist ;
return ( ene ) ;
}
/* tipo di dati usato per lintegrazione */
typedef struct {
dvect_ty pos ;
dvect_ty vel ;
} deriv_ty ;
/* ---* compute_deriv ()
*
* Calcola la derivata della posizione e velocita
*/
deriv_ty compute_deriv ( planet_ty p )
{
deriv_ty d ;
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 ) ;
d [ 0 ] . pos . x =
d [ 0 ] . pos . y =

632

p [ 0 ] . vel . x ;
p [ 0 ] . vel . y ;

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

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 ;

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 ;

/* calcolata una volta sola */

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

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

/* 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)
*/
void rk4 ( double dt , planet_ty plan )
{
register int ip ;
double
dt2 ;
planet_ty
p_tmp ;
deriv_ty
k [ 4 ] ;
dt2 = 0 . 5 dt ;

/* calcolata una volta sola */

/* 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 ;

yaC-Primer: Pianeti con Runge-Kutta

(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 ;

/* x(t+1) = x(t) + dt [k0/6 + k1/3 + k2/3 + k3 /6] */


for ( ip = 0 ; ip < 2 ; ++ip ) {
plan [ ip ] . pos . x += dt (
k [ 0 ] [ ip ] . pos . x + 2 . 0 k [ 1 ] [ ip ] . pos . x
+ 2 . 0 k [ 2 ] [ ip ] . pos . x +
k [ 3 ] [ ip ] . pos . x
/ 6.0;
plan [ ip ] . pos . y += dt (
k [ 0 ] [ ip ] . pos . y + 2 . 0 k [ 1 ] [ ip ] . pos . y
+ 2 . 0 k [ 2 ] [ ip ] . pos . y +
k [ 3 ] [ ip ] . pos . y
/ 6.0;
plan [ ip ] . vel . x += dt (
k [ 0 ] [ ip ] . vel . x + 2 . 0 k [ 1 ] [ ip ] . vel . x
+ 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 ;
}


Note sul programma


planet_ty

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 */

Questa istruzione definisce la variabile planet come puntatore ad un oggetto di tipo


planet ty. Questa variabile `e usata per creare un array di oggetti di tipo planet ty,
in questo caso unarray di dimensione 2. Lallocazione della memoria viene fatta con la
funzione create planet():
planet = create_planet ( 2 ) ;

/* crea spazio per il pianeta */

635

yaC-Primer: Pianeti con Runge-Kutta

(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 ] ) ;

Siccome la funzione write out() prende come terzo parametro un puntatore ad un


oggetto di tipo planet ty dobbiamo passare lindirizzo dei singoli pianeti.
for ( tim = 1 ; tim < tim_max ; ++tim ) {
rk4 ( dt , planet ) ;
....
}

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 p `e un puntatore ad un array di strutture, di conseguenza ogni elemento


dellarray `e una struttura e quindi per accedere ai suoi campi bisogna usare loperatore
.. Nel secondo caso il campo `e a sua volta una struttura per cui si deve usare di
nuovo loperatore . per poter accedere ai campi.
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 ) ;

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 ;

Lalgoritmo di Runge-Kutta si applica ad equazioni differenziali del primo ordine. Per


passare da unequazione del secondo al primo si introduce una variabile ausiliaria, che
in questo caso `e la veloctit`a, e si scrivono le equazioni in termini della derivata prima di
` quindi utile definire un nuovo tipo per la derivata prima di tutte le
tutte le varibili. E
variabili. Siccome questo tipo serve solo per le funzioni di integrazione lo definiamo in
questo punto in modo che sia valido solo da qui in poi e quindi sia locale alle funzioni
di integrazione.

636

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

deriv_ty compute_deriv ( planet_ty p )


Questa funzione calcola la derivata della posizione e velocit`a di ciascun pianeta. Questa
viene messa in un array di 2 oggetti di tipo deriv ty. La funzione ritorna il puntatore
allarray. La funzione `e definita prima delle funzioni rk2() e rk4(), dove viene usata.
Non `e quindi necessario usare un prototipo.
d [ 0 ] . pos . x =

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 ;

derivata delle posizioni e velocit`a dei pianeti. La variabile d `e un array di strutture


e quindi ogni elemento dellarray `e una struttura. Per accedere ai vari campi si usa
loperatore ..
register int ip ;
La variabile ip `e usata come indice di un ciclo. Questa istruzione richiede al compilatore
di usare per questa variabile una locazione di memoria ad accesso veloce per migliorare
la velocit`a di esecuzione.
planet_ty

p_tmp ;

Lalgoritmo di Runge-Kutta richiede il calcolo delle derivate delle posizioni e velocit`a


dei pianeti in posizioni di prova. La variabile ausiliaria temporanea p tmp `e usata a
questo scopo.
kappa = compute_deriv ( plan ) ;
La variabile kappa `e un puntatore ad un array di 2 oggetti di tipo deriv ty che contengono la derivata delle posizioni e velocit`a dei pianeti nella configurazione plan.
deriv_ty

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

yaC-Primer: Pianeti con Runge-Kutta

(Rev. 2.0)

p_tmp [ ip ] . vel . y = plan [ ip ] . vel . y + dt2 k [ 0 ] [ ip ] . vel . y ;


}
k [ 1 ] = compute_force ( p_tmp ) ;

La funzione compute force() restituisce un puntatore ad un array di oggetti di tipo


deriv ty, quindi k[0] `e un array di strutture. Analogalmente k[1], k[2] e k[3].
La variabile k pu`o essere pensata come una matrice 4 2 di strutture in cui il primo
indice indica il k dellalgoritmo ed il secondo il pianeta. Per accedere ad un elemento
bisogna specificare entrambi gli indici, ad esempio k[0][0]. Per accedere ai campi
della struttura si usa loperatore ., ad esempio per il primo pianeta k[0][0].pos.x,
k[0][0].pos.y, k[0][0].vel.x e k[0][0].vel.y.

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

Potrebbero piacerti anche