Tutorial analisi spettri e correlazioni

Introduzione

Questo quaderno si inserisce nel materiale didattico che verrà fornito agli studenti del secondo modulo del corso di laboratorio di fisica 3A (per gli amici Labo 4).

Ho scelto di ripercorrere l'analisi di un esperimento obbligatorio lo scorso anno ma facoltativo quest'anno poiché al suo interno contiene tutte le tecniche da utilizzare nelle altre attività ma al tempo stesso non fornisce un template già pronto da utilizzare come una black box per analizzare in automatico i dati raccolti.

Nel caso durante il corso nascessero esigenze particolari riguardo a qualche dubbio nell'effettuare una certa procedura, oppure vi trovaste a dover lavorare con dati in formati strani, esattamente come durante lo scorso semestre, potete contattarmi e d'accordo con gli altri tutor e con i docenti, vedrò di venirvi incontro nel miglior modo possibile. La mia mail è scarsi@studenti.uninsubria.it

Scopo

In questo quaderno verranno presentati alcuni spunti necessari per poter costruire istogrammi relativi a spettri, fittarne i picchi e costruire istogrammi bidimensionali per studiare le correlazioni.

Il dataset utilizzato è quello relativo all'analisi della lifetime del $^{57}$Co (che quest'anno risulta essere una attività a scelta), ma le medesime tecniche si applicano ad un generico contesto in cui si hanno dei rivelatori da cui, sotto certe condizioni di trigger, viene estratto il massimo della waveform e si deve valutare la distribuzione dei massimi (come per esempio nel contesto della spettroscopia gamma, di cui vedremo un assaggio nell'esercitazione).

Strumenti

L'analisi viene effettuata utilizzando Python 3 ed in particolare moduli numpy, matplotlib e scipy per la gestione e la visualizzazione di dati numerici.

Setup sperimentale e contesto

Il setup sperimentale è costituito da due tubi fotomoltiplicatori (PMT) connessi ad una catena elettronica di amplificazione e shaping. Ogni qualvolta almeno uno dei due PMT è sopra una soglia prestabilita, vengono acquisiti i massimi delle forme d'onda di ciascun canale.

Lo stato eccitato del $^{57}$Fe può decadere o emettendo un fotone da 136 keV (parte sx dello schema) che può venir rivelato da un PMT oppure emettendo in successione due fotoni da 122 keV e 14keV, che possono venir rivelati ciascuno da uno dei due PMT.

Formato dei dati

I dati sono dei files di testo (in formato ASCII): ogni riga corrisponde ad un evento (ovvero ogni volta che almeno uno dei due PMT ha avuto valore sopra soglia), mentre ogni colonna contiene una certa quantità. Aprendo un file, esso ha la forma

...
00000022 1556869569 00117 00479 02536 00924
00000023 1556869569 09368 00732 03898 00972
00000024 1556869569 00008 00054 11311 01006
00000025 1556869569 00002 00051 10155 01002
00000026 1556869569 00178 00847 15232 00840
00000027 1556869569 00010 00083 15228 00914
00000028 1556869569 00000 00000 00003 00052
00000029 1556869569 00029 00773 15227 00876
00000030 1556869569 00763 00208 00002 00053
00000031 1556869569 00872 00208 00002 00057
00000032 1556869569 00081 00851 08307 01006
...

dove la prima colonna rappresenta il numero dell'evento, la seconda colonna il tempo unix) (ovvero il numero di secondi trascorsi dalla mezzanotte del 1° Gennaio 1970) mentre le successive colonne rappresentano il massimo del segnale e il relativo tempo rispetto al trigger per ciascuno dei due PMT.

Analisi dei dati

Iniziamo caricando (import) i moduli che verranno utulizzati durante l'analisi e definendo un alias per comodità nel richiamarli.

Load dei dati

Carichiamo i dati utilizzando il metodo np.loadtxt. Il carattere "_", pur essendo un nome lecito per una variabile, viene convenzionalmente utilizzato per indicare una variabile di nessuna importanza. Infatti per caricare una matrice con N colonne ed utilizzare l'opzione unpack=True (che permette di memorizzare ciascuna colonna in un diverso vettore) è necessario passare tante variabili quante sono le colonne, indipendentemente dal fatto che alcune non sono interessanti.

Inoltre poichè il tempo è espresso in unità di tick del digitizer ed il digitizer campiona con una frequenza di 500MHz, si ha che 1 tick = 2 ns

Vediamo che si tratta di un dataset con quasi 8 milioni di eventi: è naturale che ci voglia qualche secondo a caricarli in memoria

Visualizzazione dei dati

La prima cosa da fare quando si hanno dei dati è sicuramente guardarli. Uno potrebbe essere tentato di costruire un plot ampiezza vs tempo

Ma purtroppo non si vede un granchè. Possiamo provare a plottare singoli punti non uniti da linee, eventualmente con marker più picolo

Il problema risiede nel fatto che questi dati non sono da guardare in questo modo

Costruzione degli spettri

A noi infatti interessa studiare la distribuzione dei massimi, sapere in un certo intervallo di valori quanti eventi sono stati registrati: dobbiamo costruire un istogramma

Matplotlib mette a disposizione il metodo hist che permette di effettuare l'istogramma di un vettore

Da questo istogramma si vede poco e nulla. Proviamo ad aumentare il numero di bin

C'è ancora un po' da lavorare per ottenere lo spettro. Vi segnalo che anche numpy consente di effettuare istogrammi. Personalmente preferisco mantenere distinta la parte in cui vado a costruire l'istogramma e la parte in cui vado a plottare l'istogramma costruito, pertanto di seguito tutti gli istogrammi saranno creati con il metodo np.histogram.

Notiamo come i due metodi (matplotlib e numpy) accettino gli stessi argomenti, ad eccezione dei parametri relativi alla grafica. Infatti il metodo di matplotlib va a richiamare proprio quello di numpy.

Istogrammi 1D e spettri

Il metodo np.histogram accetta come argomento obbligatorio il dataset da istogrammare. Possiamo poi specificargli alcuni parametri opzionali, come per esempio il numero di bin o il range. Gli argomenti che ritorna invece sono il numero di eventi per ciascun bin e il bordo dei bin.

Il secondo vettore ha un elemento in più del primo e, per ottenere le coordinate $xy$ da plottare, è necessario sfasare tutti i punti, ad eccezione dell'ultimo, di mezzo bin.

Quando devo disegnare più curve in uno stesso plot con impostazioni analoghe, per comodità definisco un dizionario che le contiene e le passo alla funzione plt.plot effettuando l'unpacking dei dizionari mediante il doppio asterisco.

Per poter disegnare un istogramma con il metodo plt.plot è necessario specificare l'opzione drawstyle (o la sua abbreviazione ds) a steps-mid, che indica di tracciare il grafico a gradini e che il gradino debba essere centrato rispetto alla corrispondente ascissa.

Da questo istogramma si vede poco e nulla. Prima di tutto al di sopra di 14 000 ADC potrebbe esserci una saturazione. Proviamo anche a mettere l'asse $y$ in scala logaritmica (ax.set_yscale("log"))

Proviamo anche a definire un range entro cui effettuare il l'istogramma

Et voilà! Ecco il nostro spettro!

Che cosa mostra questo plot? Questo plot ci mostra la distribuzione dei messimi misurati dai due PMT, ovvero ci dice in ogni intervallo di energie (misurate in unità arbitrarie di ADC) quanti eventi sono stati registrati. L'area sottesa a questo grafico è invece il numero totale di eventi utilizzati (quindi quelli che non sono stati tagliati con la selezione in range).

Da questo plot si vedono emergere alcuni picchi, che dovrebbero corrispondere ai fotoni che vengono emessi.

Istogrammi 2D

I biplot o istogrammi bidimensionali possono essere costruiti facilmente sia usando matplotlib che numpy utilizzando funzioni analoghe. In questo caso utilizziamo il metodo plt.hist2d, che accetta come argomenti due vettori, oltre a parametri analoghi al caso monodimensionale (numero di bin, range, ...). Inoltre specifichiamo la mappa di colore che intendiamo utilizzare.

Il biplot effettua un binnaggio lungo i due assi ed in ogni quadratino viene riportato il numero delle volte che il primo canale rientrava in quel bin lungo x ed il secondo canale il quel bin lungo y, ovvero il numero di volte che il primo PMT ha visto una certa energia ed il secondo PMT un'altra certa energia. Il numero di volte viene codificato con un colore.

Questo tipo di plot sono molto potenti in quanto permettono di studiare le correlazioni tra i due canali. Nel nostro caso abbiamo due fotoni che possono venire emessi in coincidenza (a distanza di pochissimo tempo) e venir rivelati uno per ciascun PMT: questo giustifica gli addensamenti che si notano.

Iniziamo quindi a provare a costruire un istogramma bidimensionale

Spesso può essere utile consultare gli istogrammi in scala logaritmica per accentuare piccole differenze. Per farlo è sufficientemente specificare tra gli argomenti norm = mpl.colors.LogNorm()

Notiamo che tale grafico ha qualcosa di visualmente fastidioso, in particolare introduce una discontinuità, alternando bianco e blu. Ciò è legato al fatto che dove si ha il bianco si hanno 0 conteggi, il cui logaritmo non è definito. Poiché i nostri dati sono sempre riferiti a conteggi, quindi sono 0, 1, 2.... può essere utile ridefinire a 0 il caso in cui vi siano stati 0 conteggi, mantenendo così l'uniformità. Il modulo copy ci permette di copiare un oggetto in modo completo, in modo tale che noi possiamo modificare una versione clonata di un oggetto di libreria.

Talvolta può essere comodo (soprattutto per sovrapporre due istogrammi bidimensionali, nel cui caso servirebbero "4 coordinate") costruire un "boxplot", ovvero un istogramma 2D, dove l'informazione sul numero di conteggi non è codificata nel colore ma nella larghezza del marker. In questo senso, utilizzando differenti colori si possono sovrapporre più istogrammi bidimensionali.

Un'altra cosa utile può essere di tracciare delle curve di livello, ovvero delle curve che uniscono punti alla stessa quota. Anche questa operazione può essere fatta comodamente sfruttando plt.contour.

In questa cella mostro anche come si possano disegnare dei rettangoli utilizzando i patches e come si possano disegnare delle frecce con testo utilizzando plt.annotate.

Dalla mia relazione:

Le regioni 1 e 2 corrispondono al caso in cui un fotone da 122 keV giunga all’interno dell’area sensibile di un rivelatore, estragga un elettrone per effetto fotoelettrico ma il raggio X di diseccitazione fugga nel secondo detector. Benché non siano ugualmente sensibili ai fotoni a 122 keV, tale effetto può verificarsi per entrambi i detector. Si segnala come possa anche venir emesso un unico fotone da 136 keV: tuttavia, essendo di un ordine di grandezza inferiore in intensità rispetto a quello da 122 keV, tale fotone non viene rivelato sottoforma di picco e contribuisce ad un allargamento della distribuzione del picco da 122 keV. La regione 3 corrisponde al caso in cui nel detector spesso venga rivelato un fotone da 122 keV e nel rivelatore sottile venga rivelato un fotone da 6.4 keV: quest’ultimo corrisponde ad un raggio X caratteristico del $^{57}$Fe presente all’interno della sorgente prodotto dal fotone a 14.4 keV che non riesce a fuoriuscire dalla sorgente. Infine la regione 4 rappresenta la regione di interesse: in tale regione il rivelatore spesso rivela il fotone da 122 keV mentre quello sottile il fotone da 14.4 keV. Tali fotoni vengono emessi in cascata nel decadimento. Solo per questi eventi verrà valutata la differenza temporale usata per il calcolo della lifetime dello stato eccitato a 14.4 keV.

FIT

Una volta che sono stati definiti gli spettri, il passo successivo è di effettuare il fit in una regione ben precisa. Per essere sicuri di andare a considerare solo eventi di interesse, sono state effettuate le proiezioni solamente all'interno dei rettangoli: questo è un modo per effettuare dei tagli, per selezionare solamente un sottoinsieme dei dati.

I picchi verranno fittati con una funzione gaussiana: inizio quindi definendo la funzione gauss con cui andrò ad effettuare il fit.

Successivamente, poichè vi sono una serie di operazioni da ripetere per ciascuna regione, definisco una funzione che mi permette di mantenere compatto il codice.

Vado quindi a richiamare la funzione per ciascuna regione

Retta di calibrazione

Una volta che sono stati individuati i picchi con il fit gaussiano, è possibile convertire gli spettri da unità arbitrarie di ADC ad energie in keV. Sapendo l'energia vera dei picchi (vedi sopra) ed avendo stabilito i picchi che si vedono quali energie dovrebbero avere, è possibile plottare l'energia in unità di ADC (la $x$ del picco, la $\mu$ estratta dal fit) in funzione dell'energia vera. Questa retta costituisce la retta di calibrazione (più precisamente l'inverso della retta di calibrazione, ovvero quella funzione che accetta come argomento un'energia in ADC e restituisce l'energia in keV.

Inizio quindi definendo la funzione di fit. È vero che per una retta esiste la funzione polifit, ma ci sono state delle volte in cui mi ha dato problemi, e comunque non si possono specificare gli errori.

Successivamente definisco dei vettori contenenti i valori veri dei picchi, i valori estratti dal fit e gli errori.

Procedo quindi effettuando il fit ed andando ad effettuare il plot

Siccome io sono pigro, questo è un modo di farsi aiutare dal pitone a formattare le tabelle $\LaTeX $, cosicchè sia sufficiente copiare questo output ed incollarlo nella relazione, senza dover inserire ogni numero a mano.

La keyword lambda permette di definire una sorta di funzione tutto su una riga. In genere le uso quando c'è un conto da fare, per esempio una propagazione degli errori, in modo tale da non doverlo ripetere più volte, ma al tempo stesso non dovermi scomodare a definire una funzione vera e propria per uan sola riga.

In questa cella vado semplicemente a definire le regioni che utilizzerò per calcolare il tempo di vita. La prima metà della cella è già stata vista prima e riportata per comodità, nella seconda parte vado praticamente a fissare e visualizzare le soglie (definite in termini di $\sigma$. Stiamo considerando solamente la regione di interesse

Calcolo lifetime

Con taglio

Per calcolare la lifetime sono state calcolate le differenze nei tempi di arrivo relative ai due PMT nella regione di interesse, con bordi fissati in termini di $\sigma$, come descritti nella cella superiore.

Inizio quindi definendo la funzione esponenziale con cui fare il fit. Anche in questo caso, poiché devo ripetere una medesima operazione per 4 volte, vado a definire una funzione per praticità.

Dopo aver valutato le differenze nei tempi di arrivo per gli eventi scelti, vado a studiarne la distribuzione (ovvero a fare un istogramma). Infine viene fittata la parte destra dell'istogramma con un esponenziale decrescente, la cui $\tau$ rappresenta la vita media.

Le due celle seguenti sono uguali con una sola differenza sostanziale: nella prima vado a ciclare con un for per ogni evento per stabilire se la condizione è soddisfatta. Poiché ho definito 4 regioni, vado a fare $8*4 = 32$ milioni di iterazioni, mentre nel secondo caso uso la vettorializzazione. Il magic commando %%time permette di calcolare il tempo per effettuare l'operazione: notiamo come nel primo caso stiamo parlando di un minuto, mentre nel secondo caso di un secondo, da cui impariamo la lezione che, se non strettamente vitali, I CICLI FOR SI DEVONO EVITARE, soprattutto quando si lavora con vettori e soprattutto se questi hanno una dimensione notevole.

Modo da evitare

Modo smart

Senza taglio

Agli istogrammi appena visti è stato anche sovrapposto l'istogramma relativo a tutti i dati a disposizione: i dati di interesse, che hanno permesso di calcolare un tempo coerente con quello atteso praticamente non si vedono, sembrano una minima parte. L'istogramma logaritmico consente di vederli meglio.

Non si può che restare meravigliati dalla potenza della tecnica delle coincidenza, che ha permesso di selezionare una piccolissima frazione degli 8 milioni di eventi a disposizione, ma che hanno consentito di misurare il tempo di vita.

Appendice A: l'importanza di guardare i dati

Questi dati hanno la particolarità di avere alcuni indici statistici uguali fino alla seconda cifra decimale (media x/y, std x/y e indice di correlazione di Pearson). Questo semplice esempio vuole mettere in luce l'importanza di guardare i dati, non limitarsi a farseli riassumere da qualche indice statistico

Appendice B: il problema della funzione spigolosa

Talvolta, per esempio quando si effettua il fit gaussiano di un picco, può accadere di avere pochi punti a disposizione. Ricalcando quanto descritto nel tutorial del prof. Mascagna, generiamo un esempio di dati e vediamo come approcciare il problema

Proviamo ora ad eseguire il fit con la gaussiana

Per risolvere il problema creiamo un vettore "più denso", che abbia i medesimi estremi, ma con più punti in mezzo, in modo tale che la funzione venga disegnata più smussata. Tale problema in linea teorica l'abbiamo già incontrato con la retta di calibrazione ma, trattandosi di una retta, l'avere pochi punti in mezzo non ha alcuna rilevanza: bastano infatti due punti per definire una retta nello spazio.