Laboratorio di Calcolo 2

proff. A. Andreazza, E. Spoletini, A. Vairo

Università degli Studi di Milano

Anno Accademico 2003/2004

Lezione 4

Introduzione

Scopo di questa sessione di laboratorio e' imparare ad utilizzare il programma ROOT per l'analisi dei dati simile al programma PAW usato al primo anno. La principale differenza e' che ROOT usa il C++ (un superset del C, con estensioni del linguaggio adatte alla programmazione ad oggetti) come linguaggio per l'esecuzione dei comandi.
Oltre ad avere un'interfaccia grafica molto piu' flessibile, ROOT ci dara' la possibilita' di parlare di programmazione ad oggetti. In piu'  fornisce oggetti utili per l'istogrammazione e la generazione di numeri casuali.
 

Oggetti e strutture

A lezione avete visto come il C permetta di definire degli aggregati complessi di dati sotto forma di strutture.
Un esempio di struttura puo' essere un punto bidimensionale:
struct punto {
  float x;
  float y;
};
Dopo aver definito questa struttura, noi possiamo definire delle variabili di tipo punto ed accedere alla loro struttura interna, ad esempio:
  struct punto XY1, *XY2;      // dichiarazione una variabile e di 
                               // un puntatore alla struttura
  XY1.x=1.;                    // inizializzazione di XY1
  XY1.y=2.;                                           
  XY2 = (struct punto *)malloc(sizeof(struct punto)); 
    // L'operatore sizeof funziona anche sulle strutture. Ricordarsi di
    // fare il cast di malloc
  *XY2 = XY1;    // esiste l'operatore di assegnazione di strutture 
                 // (copia membro a membro)
  printf("Le coordinate del punto 2 sono: (%f,%f)\n",XY2->x,XY2->y);
                 // per i puntatori l'operatore per accedere alle variabili
                 // e' "->" invece di "."
Il tutto e' molto piu' snello se usiamo un typedef per abbreviare la definizione:
typedef struct punto PUNTO;
In tal caso le istruzioni diventano semplicemente
  PUNTO XY1, *XY2;             // dichiarazione una variabile e di 
                               // un puntatore alla struttura
  XY1.x=1.;                    // inizializzazione di XY1
  XY1.y=2.;                                           
  XY2 = (PUNTO *)malloc(sizeof(PUNTO)); 
    // L'operatore sizeof funziona anche sulle strutture. Ricordarsi di
    // fare il cast di malloc
  *XY2 = XY1;    // esiste l'operatore di assegnazione di strutture 
                 // (copia membro a membro)
  printf("Le coordinate del punto 2 sono: (%f,%f)\n",XY2->x,XY2->y);
                 // per i puntatori l'operatore per accedere alle variabili
                 // e' "->" invece di "."
Le strutture permettono solamente di raccogliere un insieme di dati, lasciando agli utenti la massima liberta' (ovvero, nessuna protezione) nella loro gestione. I linguaggi di programmazione piu' recenti (C++, Java...) tendono ad enfatizzare delle altre entita' dette oggetti. Mentre una struttura pone l'enfasi sui dati, gli oggetti pongono l'enfasi sul comportamento: un oggetto e' caratterizzato da un insieme di funzioni, dette metodi, attraverso le quali puo' operare. La sua struttura interna contiene dei dati che pero' non sono accessibili all'utente: quello che importa sono solo le azioni che l'oggetto puo' compiere.
Per fare un esempio, il programma ROOT definisce un tipo di oggetti chiamato TRandom per la generazione di numeri casuali.
Questi oggetti hanno un insieme di funzioni definito:
float Rndm();
restituisce un numero distribuito uniformemente nell'intervallo [0,1]
int Poisson(float m);
restituisce un numero intero secondo la distribuzione di Poisson con valor medio m
double Gaus(float media, float sigma);
 restituisce un numero secondo una distribuzione gaussiana con i valori dati di valor medio e sigma.

Per definire un oggetto di tipo TRandom, basta dichiararlo:
TRandom nomeOggetto;
per usare i metodi ad esso associati, si usa una sintassi simile a quella delle strutture:
nomeOggetto.nomeMetodo(argomenti);
Per fare un esempio, per stampare 100 numeri interi estratti da una distribuzione di Poisson con valor medio 3, potremmo scrivere il
seguente frammento di codice:
TRandom gen;
int i;
for (i=0; i<100; i++) {
  printf (" %d \n",gen.Poisson(3.));
}
Come vedete noi possiamo utilizzare gen per fare quello che vogliamo (generare numeri casuali di diverso tipo) senza sapere niente della sua struttura interna (enfasi sul comportamento e non sui dati).
E' possibile anche costruire puntatori ad oggetti, ma la questione e' un po' sottile ed in ogni caso non saranno necessari per gli esercizi.
Potete trovare una tabella riassuntiva con la selezione dei tipi di oggetti e dei relativi metodi che saranno utili per questa lezione e per la parte di simulazione. Tali tipi di oggetti sono accessibili solo all'interno di ROOT (non e' strettamente vero, ma utilizzarli nei vostri programmi implicherebbe studiare un po' di C++, che e' al di la' delle possibilita' del corso).
 

ROOT

Il programma ROOT (http://root.cern.ch) si propone come un evoluzione di PAW scritta in C++, quindi orientata verso gli oggetti. Siccome il C++ e' un superset del C, quasi tutto cio' che scrivete in C sara' comprensibile da ROOT.
Per accedere al programma e' sufficiente dare da riga di comando
root
ROOT interpreata tutto cio' che viene scritto sulla riga di comando attraverso CINT, un interprete C++ interattivo. Quindi dal prompt di ROOT e' possibile dare istruzioni in C, creare variabili, effettuare  operazioni con esse...
Per bypassare l'interprete C, ci sono un insieme di comandi speciali che iniziano con un '.'.
Il piu' semplice e' quello per terminare la sessione di ROOT:
.q
 

Macro

Per eseguire compiti complessi o ripetitivi e' possibile utilizzare delle macro. Siccome il linguaggio di ROOT e' il C++, le macro non sono altro che file contenenti frammenti di programmi in C++ (o piu' semplicemente in C, nel nostro caso).
Ci sono due tipi di macro: Le prime contengono un blocco di istruzioni delimitato da parentesi graffe.  Un esempio e' il file randfile.c. Per eseguire una macro di questo tipo e' sufficiente dare il comando
.x nomefile
Variabili definite all'interno di questo tipo di macro, rimangono accessibili anche dopo la sua conclusione.

Una macro puo' anche contenere la definizione di una nuova funzione, come la macro randread.c che definisce una funzione TH1F randread(char*). In tal caso non ha senso eseguire direttamente la macro (come facciamo a passare i parametri giusti alla funzione?). Si puo' pero' caricarla in memoria con il comando:
.L nomefile
A questo punto la nuova funzione e' nota a ROOT e puo' essere utilizzata da linea di comando o da altre macro.
Se ci si accorge che la funzione ha degli errori e si vuole modificare la macro, nelle versioni piu' recenti di ROOT, si puo':

  1. scaricare la macro dalla memoria con il comando .U nomefile
  2. modifica il file contenente la macro
  3. caricarlo di nuovo con .L nomefile
Nella versione in laboratorio invece e' necessario uscire da ROOT (con .q) e rientrare.

Poiche' ROOT esegue automaticamente l'inclusione di molti degli include file di sistema, spesso non c'e' bisogno delle dichiarazioni di #include nel file contentente la macro.

Grafica

Buona parte dei tipi di oggetti (istogramma, grafici...) che useremo sono dotati di un metodo
void Draw()
che disegna l'oggetto all'interno di un pannello.
Una volta disegnato, si possono fare diverse operazioni sull'oggetto utilizzando l'interfaccia grafica.

Per aggiungere testi, frecce, disegni... sul grafico, e' sufficiente selezionare Editor dal menu Edit. Per visualizzare le coordinate del puntatore del mouse, selezionare Options -> Event Status.

Per modificare la presentazione grafica di un oggetto e' possibile selezionarlo posizionandoci sopra il cursore  e schiacciando il bottone destro del mouse. Cio' fa comparire un menu che dipende dal tipo di oggetto selezionato ed indica le opzioni possibili. Particolarmente utili saranno il FitPanel o il DrawPanel per cambiare la rappresentazione degli istogrammi. Per spostare o ridimenzionare un oggetto, basta selezionarlo con il bottone sinistro del mouse.

Per i grafici (ad esempio quelli creati con grafico.c) il DrawPanel non funziona, ma e' possibile cambiare interattivamente le caratteristiche delle linee e dei punti disegnati selezionando SetLineAttributes e SetMarkerAttributes.

Il puntatore del mouse cambia forma a seconda dell'oggetto che si sta selezionando, e questa caratteristica permette di facilitare molto la selezione. Puo' essere utile ricordare che il puntatore a croce di solito indica che si sta selezionando un'area di sfondo, la freccia un grafico o un istogramma.

Infine, se avete fatto un bel grafico e volete salvarlo, dal menu File selezionate Save As...

Infine, l'anno scorso sono stati trovati alcuni comportamenti anomali di ROOT, quindi controllate nella pagina delle FAQ.

L'esercizio

Proviamo a verificare il teorema del limite centrale.

Tale teorema e' la base di tutto il trattamento statistico degli errori e grosso modo puo' essere formulato:
Siano x1, x2, x3, ...xN numeri casuali tutti estratti da una stessa distribuzione con valor medio Xm e r.m.s. S. Allora, per N tendente all'infinito la quantita' (x1+x2+...+xN)/N tendera' ad avere una distribuizione gaussiana con valor medio Xm e sigma S/sqrt(N).

Il teorema di fatto dice che, se si fanno tante misure, anche con errori non gaussiani, la media alla fine avra' una distribuzione gaussiana ed una precisione migliore delle singole misure.

Verifichiamo empiricamente che questo succeda.

Passo 1: costruire una macro per ROOT che scriva su di un file 25200 numeri distribuiti uniformemente tra 0 e 1.

Passo 2: leggere il file e mettere i valori ottenuti in un istogramma, verificare che la distribuzione sia corretta, il suo valor medio ed rms.

Passo 3: leggere i numeri del file a gruppi di due, fare un istogramma delle medie dei due numeri di ciascun gruppo.

Passo 4: ripetere il passo 3  leggendo i numeri in gruppi di 3, 4,...9, fare gli istogrammi delle medie e verificare l'evoluzione della forma della distribuzione e della sua rms.

Passo 5: fare un grafico del valore della rms in funzione del numero di punti mediati.

Relazione