Nel fare questo si simulerà una situazione in cui si partecipa allo sviluppo di un pacchetto di software con altri persone e bisogna integrare il proprio lavoro (componente numerica del progetto) con quello di altri.
Dal punto di vista tecnico, si svilupperanno i seguenti punti:
Inizialmente ci sarà da implementare la ricerca di zeri e solo successivamente si passerà a quella dei massimi e minimi.
Gli algoritmi così realizzati, verranno inseriti in un progetto più completo, che si occupa di fornire anche una visualizzazione della funzione e dei punti osservati.
#include <cfloat>
/*
* Tutte le funzioni restituiscono l'ascissa del punto trovato.
* Se nell'intervallo non viene trovato il punto cercato, il valore di
* ritorno della funzione e' DBL_MAX (costante contenente il massimo valore
* che una variabile di tipo double puo' contenere: e' definito in <cfloat>)
*/
double FindZero (double lowerlimit, double upperlimit, double precision,
double (*function)(double));
double FindMaximum(double lowerlimit, double upperlimit, double precision,
double (*function)(double));
double FindMinimum(double lowerlimit, double upperlimit, double precision,
double (*function)(double));
un altro sviluppatore del progetto dovrebbe poter lavorare basandosi esclusivamente su queste informazioni, senza dover conoscere i dettagli dell'implementazione dei vari algoritmi.
Quando si implementeranno le funzioni nel file algoritmi.cxx, bisognerà seguire queste specifiche. In particolare:
double laMiaFunzione( double );buona parte delle funzione delle librerie matematiche, come sin, cos, exp, log soddisfano questo prototipo.
In caso servissero funzioni non elementari, se ne possono costruire di apposite, ad esempio possiamo definire:
double sinxsux(double x) {
if ( x==0 ) return 1.;
else return sin(x)/x;
}
Le funzioni di ricerca da usare in studio sono funzioni generiche che devono operare su tutte le funzioni di una variabile, e quindi la funzione da studiare deve essere di volta in volta passata come argomento. Per fare questo il C++ utilizza i puntatori a fuzione.
Nella dichiarazione delle funzioni FindZero e simili, troviano nella lista di argomenti:
double (*function)(double)che si legge:
Ogni volta che si costruisce una funzione, si costruisce automaticamente un puntatore alla funzione stessa, che viene acceduto usando il nome della funzione. Per esempio, se volessimo usare FindZero per cercare uno zero della funzione seno nell'intervallo [2.,4.], con precisione 10-4, potremmo invocarla semplicemente con:
FindZero(2.,4.,1.E-4,sin)e lo stesso per le ulteriori funzioni da noi definite:
FindZero(2.,4.,1.E-4,sinxsux)
All'interno di FindZero la funzione passata come argomento può venire valutata con la sintassi:
(*function)(a)dove per prima cosa si applica a function l'operatore di dereferenziazione per accedere alla funzione puntata da function e poi si valuta la funzione ottenuta nel punto a.
#include <iostream>
#include <cstdlib>
#include <cmath>
#include "algoritmi.h"
using namespace std;
int main ( int argc, char* argv[]) {
double xmin, xmax;
double precisione=1.E-6;
if ( argc!=3 ) {
cout << "Usage: " << argv[0] << " x_minimo x_massimo" << endl;
return -1;
}
xmin = atof(argv[1]);
xmax = atof(argv[2]);
cout.precision(10);
cout << FindZero(xmin,xmax,precisione,sin) << endl;
return 0;
}
Analogamente si possono fare programmi per provare FindMinimum e
FindMaximum.
Alcuni punti caratteristici di questo programma:
Una volta che siete riusciti a compilare ed eseguire il programma, tornate a questo punto, implementate le funzioni di ricerca di massimi e minimi ed adattate il programmino di prova per controllarne la funionalità.
Uno dei vantaggi di questo programma è che, essendo interfacciato con ROOT permette di specificare l'espressione della funzione come una stringa da riga di comando.
I file contenenti i programmi necessari per questa lezione si trovano nel file di archivio compresso /home/comune/lab2/datafiles/studio.tar.gz: copiatelo nella vostra cartella di lavoro.
Questo file è stato compresso, per ridurre le dimensioni dei file di
archivio, lo strumento più
comune in ambiente Linux è il programma di compressione
gzip, che si utilizza nel modo seguente:
gzip NomeFile per
comprimere un file
gunzip NomeFile per
decomprimere
un file.
Dopo la decompressione, il file studio.tar.gz è stato rinominato in studio.tar, dove il suffiso tar sta per Tape ARchive.
Essi sono gestiti con il comando tar.


Per completezza, sono allegate le pagine del man di tar e gzip.
Decomprimere e estrarre tutti i file dell'esercizio da studio.tar.gz: dopo questa operazione dovreste avere una sottodirectory studiocon tutti i file necessari.
Leggere attentamente il file README con le istruzioni per compilare ed eseguire il programma.
Per provare il programma, si può tentare con la funzione sin(x)/x):
./studio "sin(x)/x" -10. 10.e si ottiene il seguente grafico:
Guardando il grafico ci si rende conto che qualcosa non va: in effetti il programma ha tutti gli algoritmi numerici encapsulati nel file algoritmi.cxx e nel relativo header algoritmi.h, ma questi non sono stati implementati in maniera accettabile.
Se avete svolto correttamente la prima parte della lezione, dovrebbe essere sufficiente copiare i files algoritmi.cxx e algoritmi.h che avete scritto precedentemente nella cartella in cui si trova studio e ricompilare.
Si noti che in studio, la stringa passata come argomento viene usata per creare un oggetto TF1 di RooT, che poi viene incapsulato in una funzione che soddisfa il prototipo richiesto da FindZero, FindMaximum e FindMinimum. In questo modo combiniamo la flessibilità di avere una libreria di algoritmi numerici indipendente da RooT, e quindi riutilizzabili in qualunque ambiente, ma di sfruttare le librerie di RooT per la grafica ed il passaggio della funzione da riga di comando.

Zero: (-9.4247779608,3.8980430911e-17) Zero: (-6.2831853072,-3.8980430911e-17) Zero: (-3.1415926536,3.8980430911e-17) Zero: (3.1415926536,3.8980430911e-17) Zero: (6.2831853072,-3.8980430911e-17) Zero: (9.4247779608,3.8980430911e-17) Massimo locale: (-7.725251837,0.12837455353) Minimo locale: (-4.493409458,-0.21723362821) Massimo locale: (-0.00036488121489,0.99999997781) Massimo locale: (0.00036376988551,0.99999997795) Minimo locale: (4.493409458,-0.21723362821) Massimo locale: (7.725251837,0.12837455353) Massimo assoluto: (0.00036376988551,0.99999997795) Minimo assoluto: (-4.4934094546,-0.21723362821)
Risolvere i seguenti problemi:
LIBS:=`root-config --libs`
INCS:=`root-config --cflags`
%.o : %.cxx
g++ -Wall -c $< ${INCS}
studio: studio.o interfaccia.o algoritmi.o
g++ -Wall -o $@ $^ ${LIBS}
Le prime righe definiscono le variabili INCS e LIBS, rispettivamente per utilizzare i file header di RooT in compilazione e le librerie di RooT al momento del link.
Poi viene definita una regola generale per creare un qualsiasi file del tipo nome.o a partire dal corrispondende file nome.cxx (il carattere % sta per qualsiasi file). La variable predefinita $< indica la prima dipendenza nella linea. In questo modo, non siamo obbligati, ogni volta che aggiungiamo un file sorgente nel nostro progetto, ad aggiungere nel makefile la riga necessaria per compilarlo.
Per linkare insieme diversi file oggetto e costruire un eseguibile, possiamo usare le variabili predefinite $@, nome del target ,e $^, lista completa delle dipendenze. Questo evita di trascrivere i nomi dalla riga con target e dipendenze alla riga con le istruzioni di compilazione: statisticamente molti errori avvengono durante questa trascrizione.
Sarebbe molto utile provare a cambiare la precisione, oppure il numero di
sottointervalli in cui l'intervallo iniziale
viene diviso prima di procedere alla ricerca dei
massimi e dei minimi in un sottointervallo. Per funzioni che oscillano
molto, potrebbe essere necessario suddividere l'intervallo iniziale in
sottointervalli piu' piccoli.
Si chiede di modificare studio.cxx in modo che il
programma
possa essere invocato con cinque parametri:
./studio funzione xmin xmax precisione numero_sottointervalli
di cui gli ultimi due opzionali, oppure con la forma
./studio
nel qual caso il programma richiede all'utente di fornire tutti i
parametri interattivamente.
Per svolgere questo punto, bisogna sapere che la definizione completa
della
funzione main è:
int main( int argc, char* argv[] )
dove argc è il numero di argomenti sulla riga
di comando (il nome del programma e argc-1 parametri).
Il
vettore di stringhe argv contiene tali argomenti. Nel
nostro
caso:
La caratteristica principale di questo punto è che il numero di sottointervalli non è noto al momento della compilazione, ma solo al momento dell'esecuzione. D'altra parte è necessario dimensionare un vettore di lunghezza pari a numero_sottointervalli+1.
Una volta realizzata questa modifica, provate a determinare numericamente π utilizzando i due metodi:
Considerare la funzione x2sin(1/√x) e vedere quanti minimi si trovano nell'intervallo [0,1] scomponendo lo stesso di volta in volta in 10, 100 e 1000 sottointervalli.