Relazione del progetto di tirocinio - CNR



UNIVERSITA’ DI PISA

FACOLTA’ DI SCENZE FISICHE MATEMATICHE E NATURALI

CORSO DI LAUREA IN INFORMATICA

Realizzazione completa di un’interfaccia grafica in HTML per un programma di emulazione di canale radio

[pic]

Candidato: Francesco Macchi

Tutore Aziendale: Francesco Potortì

Tutore Accademico: Laura Semini

Indice

1 – INTRODUZIONE……………………………………………………………………….. 3

1.1 Il problema……………………………………………………………………….. 5

1.2 Breve introduzione a Python (python.it)........................................................ 6

1.3 PyXML e NumPy………………………………………………………………… 6

1.4 L’HTML e i Browser……………………………………………………………... 7

2 – REQUISITI……………………………………………………………………………… 8

2.1 Principali requisiti……………………………………………………………….... 8

2.2 Altri vincoli……………………………………………………………………….. 9

3 – DESCRIZIONE DELL’INTERFACCIA E MANUALE UTENTE………………... 11

3.1 Descrizione dell’interfaccia……………………………………………………… 11

3.2 Manuale utente…………………………………………………………………... 14

3.2.1 Interazione……………………………………………………………... 15

3.2.2 Salvataggio e caricamento……………………………………………... 17

4 – PROGETTAZIONE…………………………………………………………………… 18

4.1 Strutture dati e loro utilizzo……………………………………………………………... 18

4.2 Perché viene inserita l’immagine nulla (None)…………………………………………. 20

4.3 Salvataggio sul server…………………………………………………………………… 21

4.4 File di configurazione…………………………………………………………………… 21

5 – MANUALE DI IMPLEMENTAZIONE……………………………………………… 22

5.1 La classe Var…………………………………………………………………….. 22

5.2 Il metodo do_Get………………………………………………………………… 23

5.3 Il metodo do_Post………………………………………………………………... 25

5.4 Il metodo insert()………………………………………………………………… 25

5.5 Il metodo substitution()………………………………………………………….. 27

5.6 Il metodo insertcodec()………………………………………………………….. 27

5.7 Il metodo check()………………………………………………………………... 28

5.8 Il metodo execution()……………………………………………………………. 28

6 – CONCLUSIONI………………………………………………………………………... 30

6.1 Ciclo di vita……………………………………………………………………… 30

6.2 Considerazioni finali…………………………………………………………….. 30

BIBLIOGRAFIA…………………………………………………………………………… 32

1 - Introduzione

Nella trasmissione di un segnale radio intervengono principalmente tre elementi: il trasmettitore, il canale di comunicazione e il ricevitore.

Il segnale che arriva a destinazione risulta solitamente alterato rispetto a quello di partenza, a causa della perdita di informazione che si ha durante la trasmissione. Per rimediare in qualche modo a questo problema, il segnale da inviare viene reso ridondante.

Esistono diversi tipi di codificatori che effettuano questa operazione con diversi algoritmi, ad ognuno di questi codificatori è associato un decodificatore capace di restituire il segnale di partenza.

In una trasmissione in ogni modo non è garantito che in seguito alla decodifica il segnale ottenuto sia esattamente quello di partenza e che quindi l’aggiunta di ridondanza nei dati sia sufficiente alla loro “conservazione”.

[pic]

Figura 1.1 “Esempio di comunicazione radio con alterazione di dati”

Facendo un esempio di codifica di segnale si può pensare di ripetere ogni bit n volte.

Con n=2 (ridondanza pari a 2/1) 11110000 diventa 1111111100000000.

Alcuni algoritmi di codifica consentono, oltre alla rilevazione, anche

la correzione degli errori.  Nell'esempio della codifica banale, è

possibile correggere l'errore se il numero di bit errati è minore della

metà del numero di ripetizioni n.

Esempio: se voglio trasmettere l’intero “1100” con ridondanza 3/1 otterrò l’intero 111111000000.

Se il segnale, passando attraverso il canale, arriva come 110110000001 potrà essere decodificato utilizzando un algoritmo che, sapendo che il segnale è stato trasmesso con una ridondanza pari a 3/1, controlla tre bit alla volta e, nel caso in cui trovi un solo bit diverso dagli altri due, cambia il suo valore.

Applicando questo algoritmo, il segnale ottenuto risulterebbe 111111000000 e successivamente, eliminando la ridondanza, si otterrebbe il segnale di partenza (1100).

Come già introdotto precedentemente però, non è sicuro che, aggiungendo ridondanza, il segnale dopo la decodifica risulti uguale a quello inviato; infatti, riferendoci sempre all’esempio di prima, se il segnale arrivato al ricevitore fosse stato 100110000001, la sua decodifica sarebbe risultata 000111000000 che, una volta eliminata la ridondanza, sarebbe uguale a 0100 (che è diverso dal segnale di partenza).

In questo caso, la codifica non è sufficiente a correggere l'errore.

Più si aggiunge ridondanza più è facile che l’errore introdotto dal disturbo del canale risulti ininfluente al momento della decodifica; questo metodo però implica che per inviare X bit se ne dovrebbero in realtà inviare n*X e per n grande diventerebbe molto costoso.

Sono stati inventati, perciò, algoritmi più efficienti per la codifica del segnale; due classi di questi algoritmi sono i codici a blocchi, che hanno la caratteristica di non agire sul singolo bit ma su blocchi di bit, ed i codici convoluzionali.

Ciò che rende vantaggiosi questi algoritmi rispetto all’esempio banale fatto prima è che la capacità di rilevazione dell’errore (Error Detection) e la sua correzione (Error Correction) sono migliori a parità di ridondanza.

Tra i codici a blocchi è particolarmente usato quello che utilizza l’algoritmo di Reed-Solomon (RS) che lavora su simboli di m bit piuttosto che sul bit. Il caso più diffuso e’ quello con m=8 (1 simbolo = 1 byte).

I codici convoluzionali sono codici binari che associano, a k bit di informazione, n bit di codice da trasmettere sul canale. Si distinguono dai codici a blocco per alcune caratteristiche fondamentali:

• k e n sono generalmente numeri piccoli (valori tipici sono k = 1 e n = 2 o 3, e di solito k, n < 7);

• gli n bit di codice non dipendono solo dagli ultimi k bit di informazione, ma anche dall’informazione precedente;

Algoritmi associati ai codici convoluzionali sono il decodificatore di

Viterbi e i metodi di punturazione e depunturazione, usati per ottenere

da un dato codice convoluzionale un codice simile a ridondanza inferiore

con un algortimo di semplice implementazione.

1.1 Il problema

Esiste una libreria scritta in ‘C’ chiamata “codec” che mette a disposizione alcuni moduli che realizzano un’emulazione di canale radio; i moduli della libreria definiscono: un generatore, vari codificatori ed i relativi decodificatori, un canale ed un confrontatore, per un totale di 10 moduli. E’ prevista l’inserzione di ulteriori moduli.

Il programma è funzionante a linea di comando, i vari componenti dei moduli descritti sopra vengono eseguiti in cascata, cioè ogni modulo viene eseguito collegato in pipe col successivo. Il primo è il generatore. L’ultimo è il confrontatore che riceve due flussi: quello della catena ed uno proveniente direttamente dal generatore di modo da confrontarli e produrre statistiche.

[pic]

Figura 1.2 "Struttura circuito"

Al fine di creare la catena di interesse, si è resa necessaria la creazione di un’interfaccia.

Per affrontare questo problema si è scelto di creare un’interfaccia html gestita da un server web scritto in Python; la scelta dell’html semplifica molto la gestione della grafica (pulsanti, tabelle, ecc) in quanto questa è in realtà gestita automaticamente dal browser.

1.2 Breve introduzione a Python (python.it)

Il linguaggio Python nasce ad Amsterdam nel 1989, dove il suo creatore Guido Van Rossum lavorava come ricercatore. Nei suoi dieci anni di vita, si è diffuso in tutto il mondo. In Italia, la "comunità Python" era veramente ristretta. Nel 1999 l'Italia si è svegliata dal suo torpore e sembra che l'interesse stia crescendo.

Python è innanzitutto un linguaggio di script pseudocompilato. Questo significa che, similmente al Perl ed al Tcl/Tk, ogni programma sorgente deve essere pseudocompilato da un interprete. L'interprete è un normale programma che va installato sulla propria macchina, e si occuperà di interpretare il codice sorgente e di eseguirlo. Quindi, diversamente dal C++, non abbiamo un fase di compilazione - linking che trasforma il sorgente in eseguibile, ma avremo a disposizione solo il sorgente che viene eseguito dall'interprete.

Il principale vantaggio di questo sistema è la portabilità: lo stesso programma potrà girare su una piattaforma Linux, Mac o Windows purché vi sia installato l'interprete.

Python è un linguaggio orientato agli oggetti. Supporta le classi, l'ereditarietà e si caratterizza per il binding dinamico. Ragionando in termini di C++ potremo dire che tutte le funzioni sono virtuali. La memoria viene gestita automaticamente e non esistono specifici costruttori o distruttori; inoltre esistono diversi costrutti per la gestione delle eccezioni.

Un altro importante elemento per inquadrare Python è la facilità di apprendimento. Chiunque nell'arco di un paio di giornate può imparare ad usarlo e a scrivere le sue prime applicazioni. In questo ambito gioca un ruolo fondamentale la struttura aperta del linguaggio, priva di dichiarazioni ridondanti e estremamente simile ad un linguaggio parlato. L'indentazione perde il suo ruolo inteso come stile di buona programmazione per facilitare la lettura del codice, per diventare parte integrante della programmazione che consente di suddividere il codice in blocchi logici.

1.3 PyXML e numPy

Il package PyXML è una collezione di librerie per processare XML con Python.

XML “eXtensible Markup Language”, è un formato di dati per l'interscambio di documenti dotati di struttura, sembra avere un grande impulso e probabilmente diventerà molto importante negli anni a venire. La potenza e semplicità d'uso di Python ne fanno una scelta eccellente per scrivere programmi che elaborano dati XML.

NumPy è il package fondamentale necessario al calcolo di funzioni matematiche complesse, deriva dal vecchio codice di base Numeric e può essere usato al suo posto.

1.4 L'HTML e i browser

L'HTML è un linguaggio che attraverso l’uso di tag permette di costruire pagine web. Un documento html è un file di testo dove vengono indicati tutti gli elementi che devono apparire nella pagina e le informazioni relative alla formattazione, come ad esempio il colore delle scritte, la posizione delle immagini all'interno della pagina, e altre cose di questo genere.

Chi scrive pagine html non deve preoccuparsi, se non necessario, della grafica o di come far apparire i pulsanti o le aree di testo, della fase “interpretativa” infatti si occupa il browser in uso, così, quando un generico utente visualizza una pagina web, quello che appare ai suoi occhi è l’interpretazione effettuata dal browser del codice html.

2 - Requisiti

2.1 Principali requisiti

Il sistema deve realizzare un’interfaccia per la costruzione di un canale radio formato da, un generatore di numeri casuali che creerà il segnale di partenza, codificatori che codificano il sengale, decodificatori che lo decodificano, un canale che crea disturbo nel segnale, ed un confrontare che confronta il segnale di partenza con quello di arrivo e produce statistiche.

Durante la costruzione della catena è possibile commettere vari errori dovuti all’incompatibilità di comunicazione tra moduli e al mancato rispetto di precedenza tra un modulo e l’altro, i primi devono essere controllati a run-time dell’interfaccia e quindi evitati, i secondi invece vengono controllati solo in seguito alla richiesta da parte dell’utente.

Di seguito sono riportate le precedenze da rispettare per una corretta costruzione del circuito:

• Il circuito inizia sempre con un Generatore che è unico.

• Dopo il Generatore può essere posizionato un canale o un codificatore o il Confrontatore.

• Dopo un codificatore deve essere inserito un canale o un decodificatore oppure un altro codificatore.

• Dopo un canale deve essere inserito un decodificatore o un canale o un codificatore oppure il Confrontatore.

• Dopo un decodificatore deve essere inserito un codificatore o un canale o un decodificatore oppure il Confrontatore.

• Il Confrontatore deve essere l’ultimo pezzo inserito ed è unico.

L’utente, per verificare di non aver commesso uno di questi errori durante la costruzione del circuito, deve cliccare sul tasto “Check”, a questo punto il programma effettuerà una verifica e nel caso di riscontro di errore, nel frame centrale verrà visualizzato il tipo di errore commesso.

Gli altri vincoli da rispettare riguardano i vincoli di comunicazione tra moduli, cioè il tipo di uscita di un componente deve corrispondere al tipo di entrata del successivo. Di seguito è riportata la lista dei tipi di entrata ed uscita dei moduli

|Modulo |Entrata |Uscita |

|rangen |/ |Bit |

|cencoder |Bit |Byte |

|rsencoder |Bit |Bit |

|enpuncuture |Byte |Byte |

|channel |Byte |Byte |

|vdecoder |Byte |Bit |

|rsdecoder |Bit |Bit |

|depuncture |Byte |Byte |

|harddec |Byte |Bit |

Dopo che l’utente avrà inserito un codec (codificatore o decodificatore), l’elemento successivo che deve inserire, dovrà necessariamente essere un connettore. Sono previsti 2 tipi di connettore, come mostrato in fig 2.1. Il primo viene ad esempio usato se l’uscita del codec precedente è a bit, il secondo invece se l’uscita è a byte. In questo modo l’utente sa già di che tipo dovrà essere l’entrata del prossimo codec che vorrà inserire e nel caso fosse sbagliata, nel frame centrale apparirà un messaggio di errore “modules input and output do not match”.

[pic] [pic]

Figura 2.1 “Connettore a bit e connettore a byte”

2.2 Altri vincoli

Oltre ai vincoli già descritti, per garantire un corretto funzionamento del programma ed evitare errori nella costruzione del circuito è necessario rispettare altri importanti vincoli, in questo paragrafo vengono descritti questi vincoli ed il modo in cui il programma reagisce nel caso non vengano rispettati.

• Durante la costruzione del circuito è possibile inserire nella tabella un componente non direttamente legato al resto della catena, il componente sarà visualizzato in tabella ma non inserito nella lista, l’utente potrà comunque effettuare tutte le operazioni che vuole a partire da questo componente, ma questa parte di circuito verrà inserita nella lista principale solo quando attraverso un connettore verranno collegate le estremità delle due liste.

• Ad un certo punto della costruzione, l’utente può accorgersi di dover modificare una lista non ai suoi estremi ma nella parte interna, sarà possibile inserire ciò che manca eliminando il modulo da cui si vuole iniziare a modificare la catena.

• Per quanto riguarda la sostituzione questa non può essere effettuata tra un codec e un connettore o viceversa, se l’utente esegue una di queste operazioni, verrà stampato un messaggio di errore. Per effettuare questa operazione è consigliato cancellare il componente da sostituire ed inserire il nuovo in quella posizione.

• Può succedere che l’utente inserisca per sbaglio un connettore che punta al di fuori della tabella, il programma non permette che questa azione avvenga e stampa un errore.

• Solo dopo avere effettuato il controllo, se questo risultasse positivo, sarà possibile l’esecuzione della catena premendo il pulsante “Execution”

3 - Descrizione dell’interfaccia

e manuale utente

3.1 Descrizione dell’interfaccia

[pic]

Figura 3.1 “Interfaccia”

Per la realizzazione dell’interfaccia è stato scelto di utilizzare pagine html dinamiche che modifichino il proprio stato a seconda del pulsante, della casella o dell’immagine cliccati. L’idea base è dunque quella di avere un server web che, risponde alle richieste http provenienti dalle pagine html, elabora i dati, esegue le operazioni necessarie, ed invia la nuova pagina html modificata secondo la richiesta.

[pic]

Figura 3.2 “Struttura dell’interfaccia”

Una volta che il server è attivo, basterà, da un qualsiasi browser, digitare l’indirizzo del server seguito dai due punti ed il numero di porta (2006 per default), per accedere alla pagina iniziale dove l’utente potrà scegliere il numero di righe e di colonne desiderate per la tabella in cui verrà costruito il circuito. Da questa pagina cliccando sul pulsante “Start” verrà inviato uno specifico messaggio http al server che si preoccuperà di creare la pagina attraverso cui l’utente potrà iniziare a costruire il proprio circuito.

Nella frame in alto dell’interfaccia sono disponibili i pulsanti relativi alle azioni di: sostituzione, cancellazione di un elemento o pulizia dell’intera tabella, visualizzazione dei parametri di un modulo, controllo del circuito, visualizzazione dell’help di un modulo, salvataggio, caricamento ed esecuzione della catena.

Nel lato sinistro dell’interfaccia potranno essere visualizzate una serie di immagini, queste immagini sono la rappresentazione grafica dei moduli del programma di emulazione di canale radio, quindi fino al momento dell’esecuzione del circuito l’utente non starà mai interagendo direttamente con i moduli, ma con la loro rappresentazione. Oltre alle immagini dei moduli saranno visualizzate delle immagini raffiguranti 3 diversi elementi per la costruzione di connettori, un elemento lineare e due che formano un angolo di 90°, questi elementi rappresentano attraverso una animazione la direzione del flusso. Questa è una scelta grafica che rende chiara all’utente la direzione verso cui sta costruendo il circuito. Questi elementi esistono sia per il tipo a bit che per il tipo a byte, per poter dare un immediato riscontro del tipo di entrata e di uscita dei moduli inseriti, diminuendo drasticamente il numero di errori che può essere commesso. I connettori visualizzabili nel menù sono quelli del tipo a bit, il loro tipo cambierà secondo evenienza al momento dell’inserzione in tabella.

Ogni volta che l’utente clicca su un pulsante, su una casella o su un’immagine dell’interfaccia, quello che in realtà avviene è l’invio di un messaggio http al server. Ad esempio se l’utente clicca su un’immagine per selezionarla, viene inviato il numero dell’immagine al server, che si occuperà di settare le giuste variabili ed attraverso queste creare la nuova pagina dove l’immagine selezionata risulterà ombreggiata. Se invece l’utente clicca su una casella della tabella, al server viene inviato il numero di riga ed il numero di colonna relativi alla casella, più altre eventuali informazioni, a questo punto il server effettuerà le sue elaborazioni ed i controlli necessari e se la casella è vuota ed in precedenza era stata selezionata un’immagine, nella nuova pagina al posto della casella sarà visualizzata l’immagine.

L’utente all’interno della tabella iniziare a creare un circuito, ogni volta che viene inserito un elemento, se questo non risulta direttamente legato ad un lista ne viene creata una nuova su cui possono essere effettuate tutte le operazioni. Quando viene inserito un connettore in tabella, il programma scorre le varie liste per recuperare la posizione da cui parte e la posizione verso cui punta ed eventualmente si occupa di inserirlo nella lista adeguata, se il connettore viene posizionato in modo tale da collegare due liste, queste vengono unite in una unica.

Attraverso l’interfaccia, l’utente può eseguire svariate operazioni oltre a quelle già descritte, come la sostituzione o la cancellazione degli elementi presenti in tabella, oppure per ogni modulo che non sia un connettore la visualizzazione e l’inserzione dei parametri oltre che la visualizzazione di un help, il controllo del circuito, il salvataggio e il caricamento dei lavori, e cosa molto importante l’esecuzione della catena e la visualizzazione dell’output prodotto.

Tutti questi comandi appena descritti vengono eseguiti in seguito alla pressione dei relativi pulsanti presenti nella finestra in alto dell’interfaccia, attraverso cui viene inviato uno specifico messaggio al server che si occuperà di elaborare i dati ed inviare la pagina aggiornata.

Nel caso di richiesta di esecuzione del circuito verrà aperta una nuova finestra. In questa finestra verrà visualizzata la stringa di esecuzione e l’output relativo.

Nella cartella dove risiede il server deve essere presente il file “config.xml” che è appunto il file che contiene i parametri dei moduli e tutti i dati necessari all’avvio dell’interfaccia.

I file di salvataggio sono anch’essi file xml che contengono le strutture dati relative agli elementi inseriti in tabella al momento del salvataggio e sono situati nella cartella “saved-works” sul lato server.

Nel prossimo capitolo verrà descritto il tutto molto più approfonditamente, sia dal lato utente che dal lato implementativo.

3.2 Manuale utente

Per far partire il programma è necessario dapprima avviare dalla shell la classe ServerStart, con il comando ‘python ServerStart porta’ e successivamente aprire la pagina ‘’ dove “host” indica il nome della macchina dove il server risiede e “porta” indica il numero di porta dove è stato pubblicato, e dopo aver selezionato il numero di righe e colonne desiderate per la tabella in cui verrà costruito il circuito cliccare sul tasto di avvio.

[pic]

Figura 3.3 ‘index.htm’

Dopo aver cliccato sul pulsante ‘Start’, si aprirà la finestra illustrata in figura 3.1.

La finestra è composta da 4 frames, il frame in alto contiene i pulsanti relativi alle azioni che l’utente può effettuare, nel frame di sinistra invece si possono visualizzare i moduli e i connettori che potranno essere inseriti nella tabella situata nel frame centrale mentre il frame in basso è necessario alla visualizzazione di un help breve, nel caso venga selezionato un modulo, oppure alla gestione dei salvataggi e all’inserzione dei parametri e alla visualizzazione dell’output finale.

I moduli presenti nel frame di sinistra sono divisi in codificatori, decodificatori, fili di connessione generatore, canale e confrontatore.

Di seguito è riportata la lista dei moduli con le loro icone.

|[pic] Cencoder |[pic] Vdecoder |

|[pic] Enpuncture |[pic] Depuncure |

|[pic] Rsencoder |[pic] Rsdecoder |

|[pic] Harddec |[pic] Canale |

|[pic] Rangen |[pic] Printerr |

Per semplicità codificatori e decodificatori dello stesso tipo sono stati disegnati dello stesso colore così da avere un immediato riscontro grafico, ad eccezione di “hrddec” che può essere usato al posto di “vdecoder”.

3.2.1 Interazione

Per effettuare una qualsiasi operazione il criterio da rispettare è sempre il medesimo: selezionare il modulo su cui vogliamo operare e cliccare un pulsante nel frame in alto oppure su una casella della tabella.

Se ad esempio si desidera inserire un modulo in una posizione della tabella bisogna innanzitutto selezionare il modulo dal frame di sinistra, cosicché l’elemento diventi ombreggiato, e successivamente cliccare all’interno di una casella, a questo punto l’ombreggiatura relativa alla selezione sparirà e nella casella apparirà il modulo desiderato.

[pic] ( ( ( [pic]

Figura 3.4 “Modulo prima e dopo la selezione”

Simili sono le operazioni da effettuare per quanto riguarda la cancellazione, la visualizzazione dell’help grande e la visualizzazione/inserzione dei parametri, l’unica differenza è che in questi casi l’elemento va selezionato dalla tabella.

Per quanto riguarda la “sostituzione” il criterio è leggermente diverso, in questo caso infatti gli elementi da selezionare sono due, il modulo da sostituire (dalla tabella) ed il modulo sostituente (dal frame di sinistra), ed una volta assicuratisi di averli selezionati entrambi correttamente, quindi che entrambi i moduli risultino ombreggiati, bisogna cliccare sul pulsante “Substitution”.

Per quanto riguarda la visualizzazione e l’inserzione dei parametri, dopo aver selezionato il modulo desiderato e premuto il pulsante “Parameters…” (fig 3.5) apparirà nel frame in basso la lista dei parametri relativi al codec (modulo) con i valori assunti, per modificarli sarà sufficiente scrivere all’interno della casella di testo il valore e cliccare sul pulsante “Submit”.

Il pulsante “Clean slate” cancella tutti gli elementi presenti nella tabella, ma prima di effettuare questa operazione verrà chiesto all’utente se desidera prima salvare il lavoro o intende procedere con l’azzeramento.

[pic]

Figura 3.5 “Parametri”

L’utente, prima di poter eseguire il circuito costruito premendo il tasto “Execution”, deve controllare di non aver commesso errori cliccando sul tasto “Check”, a questo punto il programma effettuerà una verifica e nel caso di riscontro di errore, nel frame centrale verrà visualizzato il tipo di errore commesso come illustrato in figura 3.6.

[pic]

Figura 3.6 “Errore nella costruzione del circuito”

3.2.2 Salvataggio e caricamento

Durante la costruzione del circuito l’utente potrebbe voler salvare il proprio lavoro, per effettuare questa operazione è sufficiente cliccare sul pulsante “Save” situato nel frame superiore, a questo punto nel frame in basso verrà chiesto di inserire il nome con cui il file dovrà essere salvato e dopo aver cliccato sul pulsante “Save file”, il programma tenterà di salvare il lavoro in una cartella situata sul lato server chiamata “saved-works”, se l’operazione ha successo apparirà nel frame centrale un messaggio di conferma, mentre se si verificassero problemi apparirà un messaggio di errore.

I file di salvataggio sono pagine xml che rappresentano le strutture dati utilizzate all’interno del programma per la costruzione del circuito.

Per caricare un file precedentemente salvato, cliccare sul tasto “Load”, nel frame in basso apparirà un text box e due pulsanti, cliccare sul pulsante “Sfoglia…”, a questo punto apparirà una finestra, come in figura 3.7, per sfogliare le cartelle e ricercare i file salvati, ed un volta selezionato il file desiderato, nel text box del frame in basso potrà essere visualizzato il percorso del file e cliccando su “Load file”, se non si verificano problemi, nel frame centrale apparirà un messaggio di conferma e nella tabella appariranno tutti i moduli relativi al salvataggio, altrimenti verrà visualizzato un messaggio di errore.

[pic]

Figura 3.7 “Caricare un file”

4 - Progettazione

4.1 Strutture dati e loro utilizzo

Le strutture dati e la loro gestione sono la parte più importante e critica della progettazione dell’interfaccia.

La struttura utilizzata per tenere traccia delle immagini inserite nella tabella è una matrice, che associa ad ogni casella un numero corrispondente all’immagine presente.

Vediamo in particolare le strutture dati utilizzate per la creazione del circuito.

La struttura principale è realizzata come una lista di terne così strutturate: (nome del modulo, posizione in tabella, parametri del modulo).

Il primo elemento che l’utente immette nella tabella viene inserito nella lista principale.

Durante la realizzazione del circuito se un utente inserisce un elemento non direttamente legato alla lista principale, ne deve essere creata una temporanea che sarà inserita in un’altra struttura, cioè una lista di liste definite come quella principale descritta sopra.

[pic]

Figura 4.1 “Esempio strutture dati”

Per come è progettato il programma non è possibile inserire un connettore isolato, pertanto questo deve essere legato ad almeno un modulo o ad un altro connettore.

Quando successivamente è immesso un connettore, avviene all’interno delle liste una ricerca dell’elemento a cui è collegato per sapere in che posizione e in quale lista deve essere inserito: a questo punto se uno dei due estremi del connettore risulta non essere collegato ad un elemento precedentemente immesso, in lista viene inserito anche un altro elemento nullo che va ad occupare la posizione dell’estremo vuoto (si veda paragrafo 4.2).

Ogni qual volta che viene inserito un connettore in una posizione tale da collegare due liste, il programma procede alla ricerca dell’elemento verso cui il connettore punta e, una volta trovato, procede a copiare il contenuto della lista che lo comprende in coda o in testa alla lista da cui il connettore parte.

I moduli non godono della proprietà dei connettori di avere un’entrata ed un’uscita ben definite. Entrate ed uscite queste vengono infatti stabilite al momento dell’inserzione in tabella e in diversi modi a seconda del caso. Il primo passo che il programma effettua è il recupero delle immagini presenti nelle quattro posizioni (sopra, sotto, destra, sinistra) intorno alla casella selezionata per l’inserzione del modulo. Una volta fatto questo, viene controllato che siano diverse da “None”: se tutte rispettano questo vincolo significa che il modulo è in posizione isolata e viene inserito in lista principale se questa è vuota, altrimenti ne viene creata una nuova temporanea.

Se le immagini intorno alla posizione dove vogliamo inserire il modulo sono due connettori che puntano uno in entrata ed uno in uscita rispetto a quella determinata posizione, e se i controlli hanno avuto tutti successo, avviene una ricerca di entrambi i connettori nelle liste e se risultano rispettivamente l’ultimo connettore ed il primo connettore di due liste diverse, il programma procede, a seconda del caso, all’inserzione degli elementi dell’una, in testa o in coda all’altra, e all’eliminazione della lista copiata, oltre che all’inserzione del modulo nella giusta posizione tra i due connettori.

Se l’utente vuole inserire un modulo in una casella contente un elemento nullo, il programma, oltre ai controlli di compatibilità necessari, ricerca l’elemento nullo corrispondente nelle liste, recupera la sua posizione e lo cancella; dopodiché inserisce il nuovo modulo in quella posizione.

Quando l’utente intende cancellare un elemento, il programma effettua una ricerca nelle liste ed una volta individuato, il componente viene rimosso. Se almeno uno tra l’elemento di destra e l’elemento di sinistra in lista è un connettore, al posto dell’elemento rimosso ne viene inserito uno nullo. Ma nel caso in cui uno dei due elementi fosse un modulo, la lista viene spezzata in due e tutti gli elementi a sinistra di quello eliminato continueranno a far parte della propria lista originale, mentre gli elementi a destra verranno copiati in una nuova lista temporanea ed eliminati dall’altra.

Se viene cancellato un elemento accanto ad uno nullo facente comunque parte della stessa lista, e di seguito a questo fossero presenti altri elementi, anche in questo caso la lista verrebbe spezzata in due, come già descritto.

Altro caso in cui avviene la divisione della lista, è quello in cui si rimuova un elemento tra due connettori ed al suo posto venga inserito un altro connettore che non congiunge più quelli di destra e di sinistra della lista. In questo caso se il nuovo connettore ad esempio venisse collegato a quello di destra, quello di sinistra verrebbe eliminato e viceversa. Ciò avviene per evitare all’utente un’operazione che altrimenti dovrebbe fare manualmente.

Se poi venissero eliminati tutti gli elementi della lista principale e quella temporanea non fosse vuota, la prima lista di questa verrebbe copiata in quella principale ed eliminata da quella temporanea.

Può accadere che in seguito all’eliminazione degli elementi, una lista rimanga occupata da soli elementi nulli, pertanto alla fine del metodo che effettua la cancellazione verranno visitate tutte le liste e, nel caso in contengano solo “None”, eliminate.

4.2 Perché viene inserita l’immagine nulla (None)?

Spesso, in seguito a determinate operazioni, viene inserito nella lista e nella tabella un’immagine o un elemento nullo (‘None’). Il motivo di questa scelta è molto semplice, in seguito ad una cancellazione, se l’elemento viene cancellato in mezzo alla lista, la sua semplice eliminazione non basta, perché se così fosse, i componenti di destra e di sinistra rispetto a quello eliminato risulterebbero adiacenti, così per evitare questo problema, al posto dell’elemento in questione ne viene inserito uno nullo per ricordare all’utente che non tutti i moduli sono collegati.

L’elemento nullo (None) viene inserito anche in seguito all’inserzione in tabella di un connettore, questo perché i connettori hanno una direzione ben precisa ed inoltre deve esistere un unico flusso, quindi viene inserito l’elemento nullo nella posizione verso cui il connettore punta per assicurarsi che il prossimo modulo che l’utente vorrà inserire di seguito alla lista in questione, venga inserito esattamente in quella posizione.

[pic]

Figura 4.2 “Esempio di inserzione dell’elemento nullo”

4.3 Salvataggio sul server

Uno degli scopi del progetto era anche permettere all’utente di salvare il proprio lavoro.

Questo è possibile, ma il salvataggio deve avvenire in una cartella della macchina dove risiede il server, anche se questo non è un problema quando il programma viene utilizzato in locale. Non è stato possibile sviluppare un programma che salvasse sul lato client, perché non esiste in HTML qualcosa che permetta al client di inviare un proprio percorso locale ad un server, per ovvi motivi di sicurezza; questa operazione sarebbe possibile utilizzando degli script, cosa vietata da specifiche di progetto.

4.4 File di configurazione

Tutte le informazioni relative ai moduli e alle immagini dell’interfaccia sono descritte in un file XML che viene caricato all’avvio.

Questa scelta semplifica l’eventuale futura inserzione di nuovi moduli, infatti chi si occuperà di tale azione non dovrà assolutamente conoscere il codice del programma, ma semplicemente aggiungere le informazioni necessarie, nel file XML di configurazione.

5 - Manuale d’implementazione

Da specifiche di progetto, il tirocinio prevedeva la progettazione di un webserver in Python, per la creazione completa di un interfaccia grafica in HTML/XML, effettuando scambi di messaggi via HTTP.

Le principali classi usate per sviluppare il programma sono due: ServerStart e RequestHandler.

ServerStart è una classe molto semplice, il suo scopo è creare un server in ascolto sull’host locale alla porta 2006 (default), oppure a quella scelta dall’utente da linea di comando, servendosi della classe RequestHandler come gestore delle richieste.

Le principali richieste che soddisfa la classe RequestHandler sono: selezione, inserzione, sostituzione, help, controllo, inserzione parametri, azzeramento, salvataggio, caricamento ed esecuzione.

Le comunicazioni tra l’interfaccia html e il server web avvengono tutte tramite HTTP ed i metodi do_GET e do_POST presenti nella classe RequestHandler gestiscono rispettivamente le richieste GET e POST dell’utente.

Una volta attivato il server e premuto il pulante “Start” dalla finestra di figura 2.1, al server viene inviato un messaggio del tipo “/start?r=10&c=15” dove ‘start’ è il comando che indica al server che l’interazione è appena iniziata e devono essere inizializzate tutte le variabili, ‘r=10’ e ‘c=15’ indicano rispettivamente il numero di righe e colonne che la tabella deve avere, in questo caso le righe sono 10 e le colonne 15. A questo punto l’utente avrà davanti una finestra con 4 frame come già descritto precendentemente, ad ogni casella della tabella nel frame centrale sarà associato un comando del tipo “/?set(numero_di_riga),(numero_di_colonna)”. I comandi associati alle caselle della tabella possono essere di due tipi, ‘set’ o ‘sset’, il primo indica che la casella è vuota e quando l’utente vorrà inserire un elemento questo sarà inserito normalmente, il secondo indica che nella casella è già presente un elemento e quando l’utente ne vorrà inserire uno diverso dovrà essere effettuata una sostituzione. Di seguito verranno descritti più approfonditamente i metodi utilizzati nel programma per gestire l’interfaccia.

5.1 La classe Var

E’ classe che contiene tutte le variabili ed i parametri necessari al corretto funzionamento del programma. Segue una breve descrizione di ognuno:

• isModify: variabile booleana che indica se l’interfaccia è stata modificata oppure no, necessaria per avvertire l’utente prima che compia azioni “distruttive”.

• checked: variabile booleana che indica se il controllo del circuito è stato effettuato.

• rc: rc[0] indica il numero di righe, rc[1] indica il numero di colonne della tabella.

• exelist: lista che contiene tutti e solo i codec inseriti, con la loro posizione in tabella e i loro parametri associati; necessaria all’esecuzione del circuito.

• insertlist: lista che contiene tutti gli elementi inseriti, codec e connettori, con la loro posizione in tabella.

• insertlistemp: lista di liste temporanee necessarie quando vengono inseriti altri elementi non legati direttamente alla lista principale.

• selection: selection[0] elemento selezionato dal frame di sinistra, selection[1] elemento selezionato dalla tabella, selection[2] e selection[3] rispettivamente numero di riga e di colonna dell’elemento selezionato dalla tabella.

• help: dizionario che contiene gli help brevi dei codec.

• images: lista che contiene i nomi delle immagini che possono essere utilizzate.

• conn: contiene gli indici della lista ‘immagini’ relativi ai connettori; necessaria alla rotazione dei connettori.

• matrix: è la matrice a cui saranno associati gli elementi della tabella.

• select: lista che tiene traccia di quale elemento è stato selezionato dalla lista nel frame di sinistra; Es: selezione[5]=1 ( è stato selezionato l’elemento numero 5 che nella lista immagini corrisponde a ‘depuncture’.

• inout: dizionario che contiene il tipo di entrata e di uscita dei codec.

• compatibility: dizionario delle compatibilità tra i moduli, ad ogni codificatore è associata la lista dei decodificatori, compatibili.

• params: dizionario dei parametri dei moduli, ad ogni modulo è associata la lista dei suoi parametri.

• lastencoder: nome dell’ultimo codificatore della lista “images”.

5.2 Il metodo do_GET()

Come già spiegato precedentemente questo è il metodo che gestisce tutte le richieste GET dell’utente, in questo paragrafo verrà spiegato brevemente come reagisce ad ognuna di queste richieste. I principali comandi a cui può rispondere sono: start, sel, set, sset, rot, substitution, cancel, check, load, save, erase, param, help, execution.

Il comando sel ha una sintassi del tipo “/?sel=(numero_selezione)”, se il metodo do_GET lo riceve quello che fa è impostare a 1 l’elemento corrispondente della lista ‘select’ e scrivere in selection[0] il nome del modulo selezionato.

Il comando set ha un sintassi del tipo “/?set,(numero_di_riga),(numero_di_colonna)”, quando il metodo do_GET lo riceve, imposta il valore corrispondente al numero di riga e numero di colonna nella matrice ‘matrix’ con il valore settato in ‘select’ e poi richiama i metodi relativi all’inserzione di un codec o di un connettore.

Il comando ‘sset’ ha una sintassi simile a quella del comando set, ma quando il metodo do_GET lo riceve vengono semplicemente impostati i valori di selection[1], selection[2], e selection[3] rispettivamente col nome dell’elemento selezionato dalla tabella, il numero di riga e il numero di colonna; a questo punto, solo se risulta selezionato anche un elemento dal frame di sinistra e nella casella è presente un’immagine bianca (None) allora viene richiamato il metodo “substitution” per effettuare la sostituzione.

Il comando ‘rot’ ha una sintassi del tipo “?/rot=(numero_di_connettore), il metodo do_GET quando lo riceve effettua una rotazione del connettore, cambia cioè l’immagine incrementando di 1 il numero di connettore da visualizzare.

I comandi ‘substitution’, ‘cancel’, ‘check’, ‘load’, ‘save’, ‘erase’, ‘param’, ‘help’, ‘execution’ sono i comandi che il metodo do_GET riceve in seguito alla pressione da parte dell’utente dei relativi pulsanti nel frame in alto.

Ricevuto il comando ‘substitution’ il metodo do_GET richiama il relativo metodo per effettuare la sostituzione.

Se invece viene ricevuto il comando ‘cancel’, viene cancellato l’elemento selezionato, se possibile, e vengono aggiornate le liste e nel caso si voglia cancellare un elemento “in mezzo” al circuito, dalla lista principale sparirà anche tutto ciò che segue da questo modulo in poi, il tutto potrà essere ricongiunto come già spiegato precedentemente.

In seguito al comando ‘help’ viene aperta la finestra dell’help relativo all’elemento selezionato.

In seguito al comando ‘check’ viene richiamato il relativo metodo per controllare che siano stati rispettati i vincoli di precedenza tra i moduli.

In seguito al comando ‘erase’ dopo la richiesta di conferma vengono riinizializzate tutte le variabili ed i parametri e quindi vengono anche cancellati tutti gli elementi dalla tabella.

In seguito ai comandi ‘save’, ‘load’, ‘param’, viene creata nel frame in basso l’interfaccia per salvare, caricare o inserire/visualizzare parametri, i comandi che poi saranno inviati da questa interfaccia, in quanto POST verranno gestiti dal metodo do_POST().

Se il metodo do_GET riceve il comando ‘execution’ quello che fa è richiamare il metodo ‘execution()’ che prenderà ad uno ad uno i valori della lista ‘exelist’ e creerà la stringa da mandare in esecuzione attraverso il metodo “Popen” del modulo “subprocess”.

5.3 Il metodo do_POST()

Come già ripetuto più volte questo è il metodo che gestisce le richieste POST dell’utente.

Le richieste che soddisfa sono principalmente 3: salvataggio, caricamento, gestione parametri.

Tutte le richieste sono proveniente dall’interfaccia che viene creata secondo esigenza nel frame in basso.

Se è richiesto un salvataggio quello che il metodo riceve è il nome del file su cui dovranno essere scritti i dati; ricevuto il nome del file viene richiamato il metodo ‘save(nomefile)’, questo metodo non fa altro che aggiungere l’estensione “.xml” al nome del file passato come parametro ed aprirlo in scrittura, una volta fatto questo viene utilizzato il metodo ‘dumps’ del package generic di PyXML per scrivere le strutture dati sul file, se l’operazione ha successo, nel frame centrale viene stampata una stringa di conferma, altrimenti viene stampato l’errore.

Se invece la richiesta è un caricamento quello che il metodo riceve è il contenuto del file aperto, a questo punto viene richiamato il metodo ‘load(file)’ che utilizza il metodo ‘loads’ dal package generic di PyXML per recuperare le strutture dati, fatto questo se l’operazione ha successo viene stampato un messaggio di conferma e l’utente potrà visualizzare il suo vecchio lavoro e continuare a lavorarci.

Per quanto riguarda l’inserzione dei parametri quello che il metodo do_POST() riceve è proprio la lista dei parametri del modulo che l’utente vuole modificare, quindi il metodo recupera il nome del modulo e la sua posizione in tabella ed una volta recuperato l’elemento in questione dalla lista vengono modificati i parametri con quelli che l’utente ha inviato.

5.4 Il metodo insert()

Questo metodo, insieme al metodo ‘substitution()’, è uno dei metodi più critici ed importanti del programma, sono infatti principalmente questi due metodi che gestiscono e controllano che non vi siano errori durante l’inserzione di elementi nella tabella.

In particolare, il metodo ‘insert()’ è pensato per l’inserzione dei connettori nella tabella e riceve come primo parametro il nome del connettore, come secondo e terzo parametro rispettivamente il numero di riga e il numero di colonna in cui l’utente vuole inserire il connettore.

Come prima cosa vi è una fase di creazione delle posizioni, la posizione da cui il connettore parte e la posizione verso cui il connettore punta e se per sbaglio l’utente avesse fatto partire/puntare il connettore da/verso l’esterno della tabella il metodo restituisce una stringa col tipo di errore commesso.

A questo punto, attraverso le posizioni, vengono recuperati il nome dell’elemento precedente, cioè l’elemento da cui il connettore parte, ed il nome dell’elemento successivo, cioè l’elemento verso cui il connettore punta.

Conoscendo posizione e nome degli elementi, deve essere effettuata una ricerca nelle liste per vedere dove effettivamente questi moduli sono stati inseriti; se l’elemento precedente viene trovato in una lista vengono effettuati due tipi di controlli, il primo controlla che non si stia cercando di inserire due connettori in uscita dallo stesso modulo, il secondo controlla se il connettore da inserire deve essere a bit o a byte; se viene trovato anche l’elemento successivo i controlli saranno leggermente diversi, dovrà cioè essere controllato che non si stia cercando di collegarsi in cima alla stessa lista e che non vi siano errori di comunicazione, cioè che l’elemento precedente e quello successivo abbiamo rispettivamente uscita ed entrata dello stesso tipo.

Se la distanza tra l’indice del prossimo elemento e l’indice dell’elemento precedente è di 1, allora il connettore viene inserito tra questi due, altrimenti il metodo ritorna una stringa contente il tipo di errore.

Se il connettore viene inserito in entrata al primo elemento di una lista, con nessun modulo in uscita, allora in testa alla lista viene inserito il connettore ed un elemento nullo, con posizione quella da cui il connettore parte.

Se invece il connettore collega due moduli facenti parte di due liste diverse, una volta effettuati i controlli di comunicazione ed una volta controllato che i moduli siano rispettivamente il primo e l’ultimo delle due liste allora alla prima lista viene appeso tutto il contenuto della seconda e questa viene poi svuotata.

Ogni volta che un elemento deve essere inserito in una delle liste, il metodo controlla se il modulo ha dei parametri, se è così li recupera e li inserisce come terzo argomento, altrimenti al posto del dizionario di parametri viene inserito un dizionario vuoto “{}”.

Se l’elemento successivo non è in lista, ed il precedente è un connettore, l’unica cosa che il metodo fa è il controllo di compatibilità e se trova delle incompatibilità restituisce una stringa contente il tipo di errore.

5.5 Il metodo substitution()

Come suggerisce il nome, questo metodo è utilizzato per effettuare le sostituzioni tra moduli.

Il metodo viene richiamato senza nessuno parametro, i parametri di cui ha bisogno infatti vengono recuperati dalla lista ‘selection’. Recuperati i parametri segue una fase di certificazione che l’utente non stia cercando di effettuare una sostituzione tra un codec e un connettore o viceversa, questa operazione non è permessa essendo molto pericolosa e nel caso viene restituito un messaggio di errore; per effettuare un operazione simile si consiglia all’utente di cancellare prima il modulo da sostituire e poi inserire il nuovo in quella posizione.

A questo punto, l’elemento da sostituire viene cercato nelle varie liste ed una volta trovato viene recuperata la sua posizione in lista e quindi l’elemento precedente e quello successivo, se esistono.

Se il modulo da inserire è un codec e quello precedente o quello successivo nella lista è un connettore, devono essere effettuati i controlli di compatibilità, se i controlli hanno successo allora si procede alla cancellazione dell’elemento da sostituire e all’inserzione, nella stessa posizione, dell’elemento sostituente.

Se il modulo da inserire invece è un connettore, allora dopo avere ricercato ed eliminato l’elemento da sostituire in una delle liste, viene richiamato il metodo “insert()”, ma non prima di avere effettuato gli eventuali controlli di compatibilità se l’elemento viene trovato in una delle liste temporanee.

5.6 Il metodo insertcodec()

Questo è il metodo che gestisce le inserzioni dei codec, riceve come parametri il nome del codec e il numero di riga e di colonna.

Come prima cosa il metodo recupera le posizioni intorno a quella dove si vuole inserire il codec con il metodo ‘get_Pos()’ e con le posizioni restituite richiama il metodo ‘get_Image()’ per recuperare i nomi delle immagini che stanno intorno al codec. Dopo di ciò viene controllato che le immagini siano tutte “None”, se non lo fossero (la variabile “inserimento” risulta uguale a “False”) questo potrebbe significare che l’utente sta cercando di inserire un codec accanto all’altro e quindi devono essere effettuati ulteriori controlli.

L’ulteriore controllo che viene eventualmente fatto è sul primo elemento della lista “insertlist” perché è possibile che si stia cercando di inserire un codec in cima al circuito collegato ad un connettore, quindi il primo elemento dovrebbe essere “None” e la posizione dovrebbe corrispondere a quella dove stiamo cercando di inserire il codec, se così fosse viene eliminato l’elemento nullo ed una volta recuperati i parametri al suo posto viene inserito il nuovo codec.

Se la variabile “inserimento” risultasse uguale a “True”, può essere direttamente appeso il codec con i suoi parametri nella lista ‘insertlist’ se questa risultasse vuota, altrimenti viene creata una nuova lista, con momentaneamente il singolo codec e i suoi parametri come unico elemento, nella lista temporanea “insertlistemp”.

5.7 Il metodo check()

E’ il metodo che effettua il controllo delle precedenze tra i moduli.

Per prima cosa elimina tutti gli eventuali elementi nulli che stanno alla fine di “insertlist”, poi controlla che tutti i moduli siano collegati correttamente l’uno a l’altro, in seguito inserisce nella lista ‘exelist’ tutti i moduli che non siano “None” o connettori presenti nella lista “insertlist” e controlla che il primo elemento sia un Generatore, l’ultimo sia un Confrontatore e che sia stato inserito almeno un Canale.

Se tutti questi controlli risultano positivi, la fase successiva si occupa di scorrere tutta la lista ‘exelist’ e per ogni codificatore trovato ricercare da quella posizione in poi il relativo codificatore.

Prima di effettuare questo controllo però viene creata una lista chiamata ‘checklist’, di lunghezza uguale al numero dei codec inseriti, con tutti elementi uguali a ‘0’.

Quando durante il controllo si hanno dei riscontri positivi, la lista ‘checklist’ viene aggiornata inserendo il numero ‘1’ nella posizione corrispondete al riscontro positivo. Se ad esempio in posizione 3 della lista ‘exelist’ viene trovato un ‘cencoder’ il metodo procede nella ricerca del suo decodificatore corrispondente cioè il ‘vdecoder’ che supponiamo venga trovato in posizione 5, a questo punto nelle posizioni 3 e 5 della lista ‘checklist’ viene inserito il numero 1.

Se durante il controllo alcuni riscontri risultano negativi, viene aggiornata una stringa, inizialmente vuota, coi tipi di errori commessi, alla fine del metodo sarà proprio questa stringa ad essere ritornata.

5.8 Il metodo execution()

E’ il metodo che si occupa di creare la stringa da eseguire.

Questo metodo può essere eseguito solo se i controlli sul circuito risultano tutti positivi; quello che fa è scorrere la lista “exelist” e scrivere gli elementi ed i parametri non nulli in una stringa chiamata “exestring”, tutti gli elementi al momento dell’esecuzione dovranno risultare collegati in pipe, per questo durante la creazione della stringa viene inserito il carattere “|” (pipe) tra un elemento e l’altro. Creata la stringa completa, l’esecuzione avverrà facendola eseguire dal comando “Popen” del modulo “subprocess”.

Esempio:

Supponiamo di aver creato un circuito composto solo da generatore, cencoder, channel, vdecoder, e confrontatore, la stringa creata sarà del tipo:

  rangen  |

        cencoder -c 7,1/2 |

        channel -q3 -c.9 --noise=awgn,-.58 |

        vdecoder -c 7,1/2 -m 100 |

        printerr -c 7 -l 0.9 -e 100 ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download