Nel programma verra` utilizzata la classe TF1 di ROOT: sara` un occasione per imparare come inserire nei nostri programmi librerie di classi predefinite. Cosa estremamente interessante perche' ROOT fornisce un sacco di classi utilissime per molte funzioni complesse.
L'approccio che utilizzeremo sara` un po' quello che vi potreste trovare ad affrontare se entrate in un grosso progetto: inizialmente vi sara` affidata la realizzazione di una libreria di funzioni numeriche da inserire in un programma piu` complesso. Per realizzare questo compito non avete bisogno di sapere come e` realmente fatto questo programma, ma solo come interfacciarvi ad esso. Il contratto tra voi ed il resto della collaborazione sara` un header file.
Durante questa sessione dopo aver copiato il file di archivio compresso studio.tar.gz dalla vostra home directory alla directory di lavoro ed aver proceduto a decomprimerlo, si dovra`:


Per completezza, sono allegati le pagine del man di tar e gzip.
Dovreste notare che ogni file .cxx e' accompagnato da un header file .h. In questo file sono messe le dichiarazioni di tutte le funzioni costruite all'interno del file .cxx e gli #include necessari alla compilazione. In questo modo, se vogliamo comunicare ad un altro programma come interfacciarsi con le nostre funzioni, bastera` includere questo header file, senza necessita` di guardare il codice vero e proprio.
Quello che sara` il vostro lavoro e` indicato nell'header file algoritmi.h. Nel file di codice da scrivere algoritmi.cxx c'e` una traccia in linguaggio umano su come si possono realizzare concretamente gli algoritmi ricorsivi suggeriti in questa lezione. In caso, abbiate idee circa altri algoritmi, provate pure a realizzarli: no importa cosa fate, basta che rispettiate l'interfaccia definita (e non facciate altro!).
# questa riga serve per invocare un comando che ci dice quali
# sono le librerie necessarie per compilare con le classi di ROOT
LIBS = `${ROOTSYS}/bin/root-config --nonew --libs`
studio: studio.o interfaccia.o algoritmi.o
g++ -o studio -Wall studio.o interfaccia.o algoritmi.o ${LIBS}
studio.o : studio.cxx studio.h
g++ -I ${ROOTSYS}/include -c -Wall studio.cxx
interfaccia.o: interfaccia.cxx interfaccia.h
g++ -I ${ROOTSYS}/include -c -Wall interfaccia.cxx
algoritmi.o: algoritmi.cxx algoritmi.h
g++ -c -Wall algoritmi.cxx
Per prima cosa, se si vogliono utilizzare le classi di ROOT, bisogna avere a disposizione gli header file. In ogni installazione di ROOT dovrebbe essere definita la variabile di ambiente ROOTSYS che indica la directory in cui ROOT e` installato. Gli header file di ROOT sono nella directory ${ROOTSYS}/include. L'opzione -I del compilatore indica appunto aggiungere ${ROOTSYS}/include alla lista delle directory in cui cercare gli header file.
Al momento di invocare il linker per costruire l'eseguibile, dobbiamo anche
fornire la versione precompilata delle librerie di ROOT. Il comando
${ROOTSYS}/bin/root-config --nonew --libs
genera una stringa contenente l'elenco delle librerie necessarie per
poter utilizzare tutte le classi di ROOT, gia` nel formato giusto
per essere passato al g++. Per comodita` abbiamo messo
questo comando all'interno di una variabile definita nel
makefile (ecco un'altra possibilita` che offre il
makefile).
Per curiosita`, potete provare ad invocare direttamente il comando
e vedere cosa viene fuori: vale o non vale la pena di mettere tutto il
risultato in una variabile?
Dopo aver compilato il programma, se lo si esegue direttamente, probabilmente si otterra` un errore del tipo:
./studio: error while loading shared libraries: libCore.so: cannot open shared object file: No such file or directoryil motivo di questo comportamento e` che, se guardate nella cartella $ROOTSYS/lib, le librerie contenute sono principalmente del tipo .so detto shared object o anche shared library .
Una shared library (che in ambiente windows e` normalmente indicata con il suffisso .dll) e` una libreria di funzioni per cui il link viene fatto al momento dell'esecuzione del programma.
Questo ha due vantaggi:
Nel momento in cui studio viene invocato, il sistema
operativo
cerca tutte le shared library di cui ha bisogno. Le shared library sono
cercate in una serie di directory di sistema piu` le directory definite
nella variabile di ambiente LD_LIBRARY_PATH. Per vedere se la variabile
e' definita e quale valore ha, bisogna dare il comando:
echo $LD_LIBRARY_PATH
Di default la directory di installazione di ROOT non fa parte di
LD_LIBRARY_PATH, che quindi deve essere definita esplicitamente con
il comando:
export
LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${ROOTSYS}/lib se la variabile
e' gia' definita
export LD_LIBRARY_PATH=${ROOTSYS}/lib
se la variabile non e' ancora definita.
Passiamo ora all'esercizio vero e proprio.

A seconda del segno di della funzione in C, possiamo stabilire se lo
zero
si trova nell'intervallo AC o in CB.
Nel nostro caso l'intervallo contenente lo zero sara' AC, dato che
la funzione e' positiva in A e negativa in C.

La seconda iterazione quindi consisterà nel valutare di nuovo la
funzione in un punto D tra A e C e decidere se lo zero si trova in AD o
in DC. Il sezionamento può essere continuato fino a quanto gli
estremi
dell'intervallo sono entro la precisione della ricerca.
Una condizione sufficiente per avere un minimo locale nell'intervallo
AC
e' avere un punto B tale che f(B)<f(A) e f(B)<f(C).

A questo punto, la valutazione della funzione su due punti intermedi D
ed E permette di ridurre l'intervallo di ricerca ad uno dei
sottointervalli
AB, DE, o BC.
Nel nostro caso l'intervallo preferito e' chiaramente BC.

Un'ulteriore suddivisione dell'intervallo permette di ridurre
ulteriormente
l'intervallo di ricerca a FG.
Un suggerimento: se avete dei dubbi sul comportamente della funzione da studiare, potete farne un grafico con ROOT (si vedano i metodi degli oggetti TF1 nella tabella), almeno per avere un'idea di quale intervallo e` interessante da studiare. Eventualmente, potete invocare anche il programma studio dall'interno di ROOT attraverso il comando: gSystem->Exec("./studio \"funzione di x\" ")
Il programma studio e' molto poco flessibile: accetta solo una
chiamata
del tipo:
./studio formula x_minimo x_massimo
ma non permette di cambiare la precisione.
Inoltre, l'intervallo iniziale viene diviso in 10 sottointervalli prima di procedere alla ricerca dei massimi e dei minimi in un sottointervallo. Per funzioni che oscillano molto, potrebbe essere utile poter suddividere l'intervallo iniziale in sottointervalli piu' piccoli.
Il vettore di stringhe argv contiene tali argomenti. Nel nostro caso:
Per convertire una stringa nel suo valore numerico, si possono usare le funzioni delle librerie standard:
double atof(char *stringa); int atoi(char *stringa);dichiarate in stdlib.h. Usare il comando man nomefunzione per avere maggiori informazioni sull'utilizzo delle funzioni delle librerie standard.
La caratteristica principale di questo punto e` che il numero di sottointervalli non e` noto al momento della compilazione, ma solo al momento dell'esecuzione. D'altra parte e` necessario dimensionare un vettore di lunghezza pari a numero_sottointervalli+1.
La soluzione che il C++ fornisce a questo problema e' l'allocazione dinamica della memoria attraverso l'operatore new (la memoria utilizzata deve poi essere rilasciata con l'operatore delete!).