1. UNIVERSITÀ DEGLI STUDI DI TRIESTE
FACOLTÀ DI INGEGNERIA
CORSO DI LAUREA SPECIALISTICA IN INGEGNERIA
INFORMATICA
IMPLEMENTAZIONE IN .NET DI UN FRAMEWORK PER
L'ANALISI DI SISTEMI BIOLOGICI BASATO SULLA
PROGRAMMAZIONE CONCORRENTE CON VINCOLI.
Laureando Relatore
Diego Banovaz prof. Luca Bortolussi
ANNO ACCADEMICO 2009 - 2010
3. Ringraziamenti
Scrivere i ringraziamenti di questa tesi è un passo importante, nel fare il
quale spero di non dimenticare nessuno.
In primis vorrei ringraziare il mio relatore, Luca Bortolussi, per avermi ac-
compagnato in questo viaggio verso la follia della durata di sei mesi. In molte
occasioni ha saputo spronarmi o fustigarmi per spingermi ad andare avanti
con il lavoro. In particolar modo è a lui che si deve la stesura corretta dell'e-
laborato; senza il suo intervento infatti non sempre avrei trovato la forza di
riordinare quella che era una matassa intricata di idee `gettate su un foglio'.
Voglio ringraziare poi la mia famiglia: mia madre Silvia, in arte Iena, la
quale ha saputo insegnarmi con dolcezza e pugno di ferro che cosa signichi
prendersi un impegno; mio padre Loris, meglio noto come Bao, che non ha
mai lesinato un gesto aettuoso o un incoraggiamento. Spero possano trarre
da questa occasione almeno una parte dell'entusiasmo e della graticazione
che loro hanno sempre saputo darmi.
Ringrazio poi mio fratello Daniele, o Menion, per avermi trasmesso innume-
revoli passioni e ben pochi sani principi. Devo sicuramente a questa `guerra
dei venticinque anni' buona parte della mia formazione caratteriale. Azzar-
dando che almeno un lettore su due abbia pensato `purtroppo', non posso
fare altro che far ricadere sul fratello malvagio la colpa.
Il mio pensiero va poi ai miei Nonni, Alda, Cornelio, Danira e Giordano per
gli insegnamenti che hanno saputo darmi nel corso degli anni e per quanto
di buono mi hanno trasmesso.
Un grande grazie va a Giordana, per avermi reso (e rendermi) ogni giorno
più felice. Un grazie per essere sempre al mio anco, nei momenti belli e in
quelli brutti.
Fine della pseudo-serietà.
Un grazie di proporzioni bibliche va al Sig. Federico Morsut (quel de Gra-
do!), per avermi accompagnato in tutto questo percorso di studi e non solo;
per esser stato sempre ad ascoltare e sempre disponibile. Unico!
Al Dott. Mr.P per l'amicizia di mille mila anni, dalla sabbionaia agli stiva-
i
4. letti di spritz!
A m3kka, per aver implementato una List in Java e per le mille altre stupi-
daggini che ci hanno portato a sghignazzare insieme.
Al buon lelìn per le mille avventure arontate insieme (quando torniamo a
Novalia?)
A PoL, perchè i motivi da elencare sarebbero troppi.
A ZaK, per essere una delle persone migliori che conosco.
Grazie a tutti i fenomeni del DMI: dal furlanissimo Ghise, al karate-kid-
spartano Alex, alla New Entry Luka, al buon vecchio Peo, al PortaNOT tr3,
ad Alice e a tutti quelli che mi odieranno per essermeli dimenticati!
Un grazie va anche a tutti i compagni di LAN della Tana: a kapa per il
falsetto potente, a Spruss per le cene di Bio, a Spino per essere un camper,
a Conza per la risata suina, a Defy per i rosik trascendentali, a Max per
la fede nel Dark Cocumber, a Iccio per la perdita dell'olfatto, a Jack per i
tormentoni immancabili, a 0rka per l'Arroganza, a minivap per l'arroganza,
a jockey per nominarmi tra le due e le trecento volte a LAN, a Diu per l'uso
scorretto di qualunque Zombie a Left, a Kane per essersi beccato con gioia
i miei Commando in CnC, a Baz per avermi fornito una scena alla Porky's
che dicilmente dimenticherò, a Turbo per lo Strip CnC con la corriera di
svedesi, a Tora per le sponte in ET, a Fil per la corretta fede nel sacro UT
serale, a Sick perchè tutto è un GdR, a Pislner Urquel per l'UT in esecuzione
automatica, a Angel per i quintali di gamberetti in salsa rosa, a Gandalf per
l'utilizzo corretto della parole Bollione, a Pietro per aver involontariamente
assaggiato, a Toom per la risata contagiosa e letale, a Gibo per il wood rivela
scritte idiote, a bubez per esser sempre più sfalcià, al Wolf per le Storie del
Lupo e alla sk0d3 per essere la prova vivente che una donna non può essere
forte ad UT, per quanto ci provi.
Ai miei cuginetti condividi-pasto/cartoni-inutili del pranzo dalla nonna, An-
gelica e Davide (e ovviamente anche a Fede e Roby). A tutta la musica, in
particolar modo agli Opeth.
Grazie anche agli Insanity Fair (Jo, Tati, Alf, Mauro) e agli Overtures (Meek,
Dani, Luka, Cum, Guitty) per le tournè/viaggi e per le incredibili serate vis-
sute insieme.
Al Gippo (alias el Bomber) per le innumerevoli volte che mi ha lasciato vince-
re a PES adducendo innumerevoli scuse improbabili, alla pazientissima Sara
e al suo acerrimo nemico Rudy.
A Niha, Maru, m3kka e Diu per la GdR experience in montagna.
A butra, per i racconti dei Mig Russi e del cemento per el controllo climatico.
A Nebbia, el Folpo, el Guru, Patachini e a tutti i compagni di carretto della
Sagra.
Un grazie anche ai Bodri per tutte le Bodrate insieme: Hale, Ophy, Sara,
ii
5. Vale, Xander, Biz, Ushti, la ragazza di Ushti, Scano, Jessica, Marta, Mono,
i ba di Mono.
Come da consuetudine i ringraziamenti son stati scritti 10 minuti prima di
mandare in stampa la tesi, quindi saranno sicuramente carichi di mancanze
e lacune.
iii
9. Introduzione
La costruzione di modelli di alcuni aspetti della realtà è sempre stata un
tassello fondamentale del metodo di indagine scientico.
Le tecniche di modellizzazione dei giorni nostri permettono di descrivere e ra-
gionare su aspetti sempre più complessi del mondo. In questo la matematica
e l'informatica hanno un ruolo fondamentale: la prima fornisce una cornice
formale entro cui costruire dei modelli, la seconda mette a disposizione po-
tenti strumenti computazionali per l'analisi.
L'aumento della potenza computazionale e il miglioramento delle tecniche di
analisi non sono tuttavia sucienti a rendere la modellizzazione matematica
pervasiva anche nelle discipline scientiche storicamente più descrittive (a
causa della loro complessità), come la biologia.
Costruire modelli formali e analizzarli a calcolatore sono attività complicate
che richiedono nozioni approfondite di matematica ed informatica, oltre che
nell'ambito specico del modello.
Per queste ragioni oggigiorno c'è la tendenza a creare degli ambienti di svi-
luppo integrato per la creazione ed analisi di modelli, che rendano questa
attività accessibile anche a non specialisti. Sull'onda di questa evoluzione
si è deciso di arontare l'argomento di questa tesi: creare un ambiente di
sviluppo per SCCP, un linguaggio per la creazione di modelli ad agenti con-
correnti ad evoluzione stocastica.
Nel corso del progetto sono stati svolte le fasi di analisi, progettazione e
realizzazione dello stesso e sono state portate tutte a termine: il framework
adesso esiste e rispetta tutte le speciche preventivate.
Lo stesso è composto da un'interfaccia graca che si interfaccia tramite XML
e CSV e tre compilatori diversi che sono stati scritti appositamente per ri-
spondere a tre diverse esigenze.
Nel primo capitolo tratteremo le nozioni teoriche che sono le fondamenta ne-
cessarie per arontare il problema: si parlerà infatti di Algebre di Processo,
Algebre di Processo Stocastiche e si daranno alcuni tratti sul lavoro svolto
dai tre compilatori che sono stati creati.
Nel secondo capitolo si aronteranno l'Analisi e la Progettazione del fra-
vii
10. mework: si partirà dalle basi teoriche esposte nel primo capitolo per proget-
tare la visione funzionale dei moduli del progetto.
Nel terzo capitolo si darà una visione esterna del progetto, si tratterà l'in-
stallazione e l'utilizzo delle diverse componenti che formano il progetto.
Nel quarto capitolo si guarderà più da vicino al progetto: si analizzeranno le
classi più importanti, si vedranno i punti di forza e di debolezza delle scelte
operate, e si vedranno a livello pratico la dierenza tra i diversi compilatori.
Il quinto capitolo sarà un'analisi prestazionale e funzionale del framework:
si studieranno le prestazioni rispetto a diversi programmi SCCP, si confron-
teranno le tempistiche e le performance dei diversi compilatori cercando di
trovare dei punti nei quali approfondire gli sforzi per migliorare il framework
stesso.
Inne, nel sesto capitolo, si trarranno le conclusioni su quanto è stato fatto.
L'intero codice è stato scritto dal tesista, eccezzion fatta per alcuni mo-
duli di supporto esterno che verranno segnalati per la corretta valutazione
dell'operato.
viii
12. Capitolo 1
Nozioni Teoriche
In questo capitolo verranno presentate sintetizzate le nozioni teoriche per la
comprensione dei capitoli successivi.
Dapprima verranno presentate le algebre di processo come formalismo per
la denizione di programmi ad agenti concorrenti. In seguito verrà esposto
CCP, come esempio di linguaggio concorrente.
Nelle sezioni seguenti si scenderà in dettaglio su sCCP e gli argomenti neces-
sari per capire le tre semantiche a disposizione del framework.
1.1 Algebre di Processo
Le algebre di processo (in inglese, Process Algebras) sono un linguaggio for-
male che permette di descrivere l'evoluzione parallela di più processi con le
conseguenti modiche del sistema stesso. Il formalismo prevede che l'evolu-
zione segua tre regole principali:
1. I processi (chiamati anche Agenti) comunicano tra loro tramite mes-
saggi (attraverso memoria condivisa)
2. La descrizione dei processi avviene tramite l'utilizzo di semplici primi-
tive, composte per formare della azioni più complesse.
3. Viene denita un'algebra che imponga delle regole per gli operatori dei
processi.
1.1.1 Operazioni dei processi
Secondo la teoria, ogni processo è composto dalla concatenazione di più azioni
atomiche; nello specico queste sono:
1
13. 1. Composizione in parallelo
2. Indicazione del canale per il trasferimento dati
3. Composizione sequenziale delle azioni atomiche
4. Restrizione di dati e comunicazioni
5. Scelta tra più possibili azioni
6. Chiamata ricorsiva ad un processo
7. Azione nulla
Nelle sottosezioni successive verrà spiegato il signicato di ognuna di queste
operazioni.
Composizione in Parallelo
Per indicare una composizione in parallelo viene utilizzato l'operatore || .
Un esempio può essere il seguente:
Agente1||Agente2
La semantica di questa azione è traducibile nell'istanziazione ed esecuzio-
ne parallela degli agenti (equivalente a processo) indicati come Agente1 ed
Agente2. Sebbene possa sembrare limitante il parallelo denito in questo
modo, basti pensare che uno dei due agenti composto da un altro parallelo
per vedere che il numero di Agenti da istanziare in parallelo con una singola
azione è illimitato.
Comunicazione
La comunicazione è un azione atomica che permette a degli agenti di comuni-
care tra loro. Si capisce subito come dei processi paralleli privi di possibilità
di comunicare risultino di scarso interesse e per questo motivo ques'azione è
così importante; solitamente vi sono due modi per due processi di comunicare:
• Tramite l'ausilio di variabili o aree di memoria condivise;
• Tramite il passaggio diretto di dati;
Il primo punto è autoesplicativo: basta dare libero accesso ad opportune aree
di memoria ed il gioco è fatto.
Per il secondo punto vanno invece deniti il canale, il usso di input e quello
2
14. di output.
Generalmente si indica il usso di input come x(v) e il usso di output come
xy
(supponendo x il nome del canale, v il vettore di dati da ricevere e y il vettore
da spedire)
A livello semantico l'elemento di input specica che il processo è in attesa di
dati in arrivo, mentre l'elemento di output specica che il processo è pronto
a inviare sul canale i dati.
Composizione in sequenza
La composizione in sequenza permette di collegare nell'esecuzione un numero
indenito di azioni. Vediamo un semplice esempio della composizione delle
azioni che abbiamo n'ora visto.
Supponiamo di voler descrivere un agente che attenda un parametro di in-
gresso per poi istanziare due agenti (A1 e A3) in parallelo: Questo processo
potrebbe venir descritto come:
x(v).(A1(v)||A3(v))
Focalizzare dati / comunicazioni
Un processo può voler limitare il numero di connessioni che gli altri agenti
possono aprire con lui: l'hiding gli permette di nascondere il canale di comu-
nicazione (o variabile) ad altri agenti. In pratica l'agente fa il wrapper di
sé stesso e nasconde all'esterno parte dei suoi canali.
Scelta
La scelta permette ad un agente di scegliere in maniera non deterministica
l'azione da eseguire.
L'operatore per la scelta è il +, un'esempio di utilizzo potrebbe essere:
P0 :- P1 + P2;
In questo caso, al momento dell'esecuzione di P0, questi sceglierà quale azione
invocare.
3
15. Chiamate Ricorsive
Una chiamata ricorsiva prevede la possibilità di far si che un processo ese-
gua sé stesso (o un altro processo), permettendo la costruzione di cicli e di
esecuzioni illimitate/innite.
Azione nulla
L'azione nulla (indicata in innumerevoli modi, come nil, null, 0) fa terminare
il processo che la invoca. Questa capacità viene utilizzata per terminare
selettivamente un agente in determinate situazioni (sicchè non rimanga in
memoria ultimata la sua esecuzione).
1.1.2 CCP: Concurrent Constraint Programming
CCP è un linguaggio di programmazione a processi concorrenti basato sui
vincoli che sta alla base di sCCP. La dierenza principale tra i due sta nella
non-deterministicità di CCP che si contrappone alla stocasticità di sCCP.
Nelle prossime sezioni verranno presentate le componenti che contraddistin-
guono il linguaggio.
Constraint Store
Il Constraint Store è la rappresentazione che CCP ha della memoria. In esso
le variabili non assumono un semplice valore, ma rappresentano un insieme
di valori che la variabile stessa può assumere.
Questa denizione è più estesa rispetto quella di Von Neumann: permette
infatti di associare alle variabili insiemi di valori, ma anche insiemi con un
solo elemento e quindi sono evidenti le maggiori potenzialità.
È possibile accedere al Constraint Store attraverso due funzioni: Ask e Tell.
Ask è una chiamata che pone una domanda al gestore della memoria, nello
specico gli chiede la validità di una formula.
Tell invece impone un nuovo vincolo al sistema espresso come formula logica
al primo ordine.
Si noti come i diversi Tell vadano ad inserire nuovi vincoli, senza in alcun
modo toccare quelli esistenti: questa condizione può portare facilmente al-
l'inconsistenza del sistema, ma utilizzata nel modo corretto porta le variabili
ad appartenere a dei domini di accettazione sempre più ristretti.
È comprensibile allora il nome di questo linguaggio:
• Concurrent: deriva dalla presenza di Agenti in un ambiente conccoren-
te;
4
16. • Constraint: deriva dall'utilizzo di una rappresentazione della memoria
a vincoli come il Constraint Store;
• Programming: indica che si tratta di un linguaggio di programmazione;
Identicate le componenti di base, si può procedere alla denizione della
grammatica del linguaggio.
La Grammatica di CCP
Di seguito viene esposto la sintassi del linguaggio nella conveniente forma di
grammatica.
P rogram ::= Declaration.Agente
Declaration ::= ε | Declaration.Declaration | p(x): −Agente
Azioni ::= ask(c) → tell(c)
Scelta ::= Azione.Agente | Scelta + Scelta
Agente ::= 0 | Agente.Agente | Scelta | Agente Agente | ∃x Agente | p(x)
Tabella 1.1: La sintassi di CCP.
Programma Programma è il punto di partenza della grammatica, in CCP
questi è costituito dalle dichiarazioni degli agenti e dall'agente iniziale.
Declaration Le dichiarazioni possono essere una, più di una o anche nes-
suna; si indicano con la forma:
N omeAgente (v ) : − Agente
Una dichiarazione rappresenta il prototipo di un Agente; contiene le diverse
azioni che fanno parte del comportamento del processo stesso e le variabili
locali che gli appartengono.
5
17. Azioni Le azioni che permettono di eettuare degli update condizionati
del Constraint Store. L'agente eettua gli Ask e i Tell al Constaint Store.
Ricordiamo brevemente quali sono le azioni compiute da ask e tell:
• Ask(condizione) permette all'agente di domandare al Constraint Store
la validità della condizione;
• Tell(condizione) permette all'agente di aggiungere al Constraint Store
il vincolo espresso in condizione;
Si veda l'esempio quì riportato per capire l'utilizzo delle guardie stesse.
if (x 5) y = 9;
diventa:
ask(x 5).tell(y = 9)
Si è deciso, sempre prendendo spunto da un'idea trovata in [1], di adot-
tare la seguente scrittura per le guardie:
[ condizione → condizione ]
La semantica scelta impone di interpretare come Ask tutte el condizione
che stanno a destra dell'operatore → , mentre ciò che sta dopo viene inter-
pretato come Tell.
La condizione prima esposta:
ask(x 5).tell(y = 9)
diventa:
[ x5→y=9 ]
Questa forma contratta non impone l'utilizzo di entrambe le azioni (Ask
e Tell), in quanto basta omettere le condizioni od utilizzare dei vincoli sem-
pre veri:
[ true → t = 10 ]
Contratto per comodità in:
6
18. [ → t = 10]
Scelta La scelta permette di associare più azioni ad un solo agente.
La scelta prevede, come si vede in grammatica, la distinzione tra le varie
azioni separate da il segno +. Ogni elemento che forma una somma è costi-
tuito da una guardia seguita da un'Agente.
Agente1 : − [x 0 → y = 0].Agente1()
+ [x == 0 → (x = 10) (y = 0)].Agente1();
Questo è la denizione di un'Agente, operante sulle variabili globali x ed
y che compie le seguenti azioni:
Se x 0, impongo un nuovo vincolo per cui y debba essere positiva;
Se x == 0, impongo un nuovo vincolo per cui x debba essere minore uguale
a 10.
Quando viene chiesto di eseguire ad un agente, questi sà quali sono le guardie
sono vere (con Ask true) e quali no: eseguirà solo le azioni corrispondenti
alle prime.
Si noti come in questo particolare esempio l'agente ha sempre e solo una sola
azione da eseguire: non è possibile infatti che entrambi le azioni superino le
guardie perchè presupporrebbe che nello stesso istante x sia maggiore a 0 e
anche uguale a 0. Nel caso più guardie siano attive contemporaneamente, la
scelta viene eettuata in modo non-deterministico.
Agente L'agente è la parte centrale non solo della grammatica, ma del
linguaggio in generale: formato da azioni e variabili locale è l'elemento sotto
osservazione da parte della simulazione. Un'Agente è formalmente composto
da agenti, guardie e scelte che a loro volta vengono composte per ottenere la
forma dello stesso da noi voluta. Un esempio di Declaration potrebbe essere:
P (x = 0) : − [true → x = 0].( P 2() || P 3() ) + P 4();
Questa viene interpretata come:
Declaration - Scelta tra (Guardia seguita da parallelo e chiamata P4)
7
19. 1.2 Algebre di Processo Stocastiche
Nel corso delle prossime sottosezioni verrà analizzato un ambiente in cui,
dato lo stato della memoria e l'indice dell'istruzione non è possibile sapere il
risultato che questa avrà, ma al massimo si potrà fare delle stime su questo
valore.
1.3 sCCP: Stocastic Concurrent Constraint Pro-
gramming
sCCP è un linguaggio per la descrizione di modelli in cui agenti interagiscono
tra loro e con l'ambiente in modo concorrente; inoltre questa interazione è
governata da leggi probabilistiche.
sCCP è un'estensione stocastica di CCP e da esso eredita le peculiarità fon-
damentali: il Constraint Store e gli Agenti. Questo linguaggio prevede la
comunicazione tra agenti come asincrona, ottenuta tramite l'utilizzo di va-
riabili globali condivise. sCCP è strutturato in modo da avere tutte le azioni
viste prima: scelta non deterministica, composizione in parallelo, chiamata
ricorsiva e in più la dichiarazione di variabili locali.
L'evoluzione temporale di sCCP è profondamente legata alle catene di Mar-
kov Tempo Continue, come spiegato in [1]. In questa sezione analizzeremo il
linguaggio e ne studieremo le dierenze con CCP.
1.3.1 Rate
Ad ogni azione interagente con il Constraint Store, viene associato un valore
∈ + che rappresenta il Rate. Il Rate è il prametro di una distribuzione di
probabilità esponenziale che descrive il tempo che l'azione impiega a venire
eseguita. Ad ogni passo si verica una Race Condition tra le diverse azioni
attive: verrà eseguita quella che impiega il tempo minore. Questa evoluzione
si può descrivere (dal punto di vista matematico) equivalentemente tramite
la scelta di un azione con probabilità proporzionale al rate, aumentando il
tempo secondo una distribuzione di probabilità esponenziale con Rate Tota-
le. Quest'ultimo si può calcolare come:
Rate(A) = π∈Actions Gπ (i) ∗ λπ
RateT otale = A∈ActiveAgents Rate(A)
Si evince che la probabilità di esecuzione di un Agente è:
8
20. Rate(a)
P (A) = RateT otale
In queste formule:
+
• λπ : CS → è il Rate dell'azione è dipendente dallo stato corrente
del Constraint Store.
• G(i) : CS → 0, 1 rappresenta un valore booleano associato alla
guardia: 1 se la condizione in Ask è vera, 0 se non lo è.
Lo scorrere del tempo, viene quindi calcolato come:
Tempo = Tempo + ∆T
∆T = −1/T ∗ log U (con U distribuzione uniforme di probabilità tra 0 e 1)
Azioni a Rate Innito
sCCP prevede la possibilità di avere delle azioni con Rate innito. Queste
azioni vengono chiamate Azioni Istantanee ed hanno le seguenti peculiarità:
• Non incrementano il tempo. (∆T = 0)
• Vengono eseguite prima di ogni azione stocastica.
1.3.2 Constraint Store
Essendo un derivato diretto di CCP, anche sCCP ha un suo Constraint Sto-
re. Per aggiungere potenzialità al linguaggio, sono state aggiunte le Stream
Variables, come denite in [1]. In applicazioni di biologia o chimica, spesso
le variabili devono poter cambiare il loro valore, non solo l'insieme dei valori
che vi si possono attribuire.
Nel sequente esempio si capisce i limiti imposti da una struttura senza Stream
Variables:
[true- X=5]
[true- X=X+1]
Questa istruzione porta ad uno stato inconsistente. Per questo motivo è stato
pensato il sistema delle Stream Variables: associando ad ogni variabile una
lista (in particolare con variabile in coda non istanziata) possiamo aggiungere
in coda i nuovi valori. Nel momento in cui ci si trovi a volere il valore di quella
data variabile, basterà guardare il valore in coda, ma la presenza all'interno
di essa di tutta la sua storia non viola il resto della teoria. In tutto il resto
dell'elaborato, nelle situazioni in cui si trova una scrittura del tipo:
9
21. [true - x=x+1]
Questa farà riferimento all'aggiunta del valore sulla destra nella lista denita
dal nome che compare sulla sinistra. Nell'approfondimento pratico vedremo
come questa gestione teorica può essere implementata con un sistema di
variabile standard a singolo valore.
1.3.3 Grammatica
P rogram = Declaration.Agente
Declaration = ε | Declaration.Declaration | p(x) : −Agente
Agente = 0 | [→ U]@∞.Agente | Scelta | ∃x Agente | Agente Agente
Scelta = π.Guardia | Scelta + Scelta
π = [G → U]@λ | [G → U]@∞
Guardia = 0 | tell∞ (c).Guardia | p(y) | Scelta | ∃x Guardia | Guardia Guardia
Tabella 1.2: Sintassi del sCCP.
L'sCCP ha una diversa grammatica per descriverlo, in cui la dierenza
principale sta nell'assegnazione dei valori di Rate. Oltre a ciò si vede come
ogni azione debba avere un peso e, essendo questo vincolato alle guardie, ogni
azione deve iniziare con una guardia. Un possibile esempio potrebbe essere
il seguente (dove @ è seguito dal Rate):
Agente1 : - [X=0-X=X+1]@{5}.Agente1() +[X3-X=X-5]@{3}.Agente1();
Agente2 : - [X0 -X=X*2]@{1/X}.Agente2();
Agente1 || Agente2
Analizziamo brevemente questo esempio, supponendo il valore iniziale di X
sia 0.
10
22. STEP X RATE AZIONE
0 0 5 X =X +1
1 1 1 X =X ∗2
2 2 0.5 X =X ∗2
3 4 3.25 X =X −5
4 -1 5 X =X +1
5 0 5 X =X +1
Per quanto quasi tutti i punti siano deterministici (in questo esempio ad hoc)
al punto 3 è stata eettuata una scelta. Nello specico le azioni attive erano:
a)[X 3-X =X -5]@{3}
b)[X 0 -X =X *2]@{1/X}
Nello specico è stata fatta una scelta a favore della prima azione per motivi
statistici:
3 0.25
p(a)= p(b)=
3.25 3.25
Ovviamente poteva anche accadere il contrario e avremmo raggiunto uno
STEP 4 con X pari a 8 e Rate totale pari a 3.125. A questo punto nuova-
mente si sarebbe eettuata una scelta, aggiornata con i nuovi valori di Rate.
3 0.125
p(a)= p(b)=
3.125 3.125
Questo banalissimo esempio mostra un'oscillazione controllata del valore di
X.
1.3.4 Le potenzialità del sCCP
Da questo esempio si desume la versatilità del sistema e la possibilità di im-
plementare praticamente ogni tipo di algoritmo: si pensi per un attimo alla
macchina URM, gemella di quella Turing, e alle 4 operazioni da essa imple-
mentate.
sCCP ha tutte le potenzialità della macchina URM, e quindi di
quella di Turing.
Dimostrazione: Per prima cosa l'enumerabilità dei registri: nella mac-
china URM questi sono inniti ma, lo sono anche in questo caso, in quanto
le variabili sono le produzioni ottenibili da una grammatica del tipo:
11
23. S : lettera S |
Ed essendo un innito numerabile posso associare a ciascuna variabile che
genero un numero intero che diventerà il numero del registro della macchina
URM.
Le operazioni della macchina teorica URM sono:
1. Z(n), n=1, 2, 3, ... . In risposta a questa istruzione la URM azzera il
registro n, lasciando gli altri registri inalterati: rn := 0.
2. S(n), n=1, 2, 3, ... . In risposta a questa istruzione la URM aumen-
ta il contenuto del registro n di una unità, lasciando gli altri registri
inalterati, rn := rn + 1.
3. T (m, n), m,n=1, 2, 3, ... . In risposta a questa istruzione la URM
trasferisce il contenuto del registro m nel registro n, tutti gli altri registri
compreso Rm rimangono inalterati, rn := rm .
4. J(m, n, q), m,n,q=1, 2, 3, ... .In risposta a questa istruzione la URM
confronta il contenuto dei registri rm ed rn se: rm = rn allora con-
tinua con l'istruzione successiva nel programma, rm = rn allora salta
all'istruzione q nel programma. Si vede subito allora che (supponendo
x = rn e y = rm )
1. Z(n) Azzeramento → [true - x = 0]
2. S(n) Incremento → [true - x = x + 1]
3. T(m,n) Scambio → [true - x = y]
4. J(m,n,q) Salto Condizionato → [x == y - true].IstrQ + [x! =y -
true]
Per aiutare a capire la dimostrazione in altro modo, è possibile esaminare un
algoritmo scritto in questo modo:
Agente1:- [x == 0 - x = x + 1]@1 . Istruzione + [x == 1 - x = x + 1]@1.
Istruzione
Agente2:- [x == 2 - x = x + 1]@1 . Istruzione + . . .
In questo caso il valore di x determina in maniera deterministica quale azio-
ne verrà eseguita: l'unica azione con la guardia attiva, infatti, sarà quella
eseguibile. Si può fare un paragone tra la variabile x di questo esempio e
l'Istruction Pointer di un normale calcolatore: agendo su questa otterremo
dei salti verso altre azioni.
12
24. Quindi, avendo vericato questo possiamo asserire che :
Eseguendo sCCP tutte le azioni della macchina URM può, co-
me CCP, per la Tesi di Church-Turing computare ogni funzione
computabile.
1.4 ITS
ITS è la seconda semantica di sCCP che è stata implementata. In questa
sezione ne analizzeremo le qualità e i limiti che, dal punto di vista teorico,
ci han permesso di crearla. Dapprima vedremo le limitazioni del linguaggio,
analizzando poi variabili di stato, transizioni e inne un esempio. È neces-
sario ricordare che si vuole giungere ad una simulazione di tipo stocastico.
Dell'argomento viene data una trattazione superciale, per approfondimenti
si rimanda a [5].
Il procedimento che si è arontato prevede la creazione di una nuova seman-
tica per il nostro sorgente sCCP, in modo da descrivere in un altro modo il
modello descritto nel sorgente stesso. In questo caso si tratta di una visione
che passa dalla visione ad agenti ad una visione a transizioni. Le tran-
sizioni, denite formalmente nella sezione specica, rappresentano in sunto
un passo eseguibile dal sistema per andare da uno stato ad un altro stato.
Nello specico possiamo creare una transizione partendo da qualunque delle
nostre azioni. Va ricordato che, non essendoci le variabili locali, ogni agente
è uguale a qualunque altro agente che implementi la stessa denizione. Per
descrivere ecientemente il sistema, si introducono le State Variables.
1.4.1 Limitazioni del linguaggio
Per poter funzionare il sistema a Transizioni il codice deve sottostare ai
seguenti vincoli:
• Non può essere utilizzata la composizione in parallelo nelle denizioni,
ma solo per denire lo stato iniziale.
• Non esistono azioni istantanee (con Rate innito).
• Ogni azione è nella forma [Ask− T ell]@{Rate}.Agente.
• Gli Agenti non posseggono variabili locali.
Queste condizioni portano a:
1. Il numero di Agenti è costante.
13
25. 2. Ogni Agente del tipo A è uguale ad ogni altro Agente del tipo A.
3. Ogni passaggio di variabili è fatto per riferimento.
Queste due asserzioni sono fondamentali ai ni pratici. Alla prima vi si arriva
tramite un procedimento molto semplice ed intuitivo. Siamo nella situazione
in cui:
• Ho N Agenti iniziali dati dallo stato iniziale.
• Non ho composizione parallela all'interno della denizione degli agen-
ti(N non può crescere).
• Ogni azione è della forma [ Ask → T ell]@{Rate}.Agente, quindi il
numero di Agenti non può calare.
In generale, queste proprietà, specie l'assenza di variabili locali, garantiscono
che le variabili di stato siano una descrizione corretta dello stato del modello
sCCP.
1.4.2 Modello ITS
Un modello ITS viene dato da una tupla ITS = (X, S, T )
Dove:
• X è l'insieme costituito dalle variabili globali
• S è l'insieme delle variabili di stato
• T è l'insieme delle transizioni
Le Variabili Globali sono le stesse viste per sCCP.
1.4.3 Le Variabili di Stato
Indicate con la lettera S nella denizione del sistema, le variabili di stato
permettono di descrivere e di gestire in maniera eciente lo stato delle tran-
sizioni del sistema. Ogni variabile di stato è associata ad una denizione di
un agente, e descrive il numero di agenti di quel tipo attualmente attivi nel
sistema. Di questa denizione, rappresenta il numero di agenti attivi che la
rappresentano. Il valore di una State Variable è dunque sempre un numero
naturale.
Ogni update delle transizioni sCCP dovrà essere modicato per descrivere
anche l'aggiornamento delle variabili di stato. Ad esempio:
14
26. A1 : − [ X 0 → X = X + 1]@{k}.A2
diventa:
[ X 0 → X = X + 1; SV 1 = SV − 1; SV 2 = SV + 1]@{k}
Incrementare una variabile di stato equivale dunque ad aggiungere un agente
del tipo associato al sistema stesso. Decrementarla equivale ad eliminarne
uno.
Supponiamo di avere il seguente agente A1 precedentemente descritto. La
guardia sarà passabile se:
X 0 ∧ SV 1 0
E il rate dato dagli agenti dello stesso tipo sarà:
A1λ = SV 1 ∗ k
In altri termini, la prima formula impone che se il numero di Agenti è pari a
0, nessuna azione è eseguibile. La seconda fa si che il Rate dato da tutti gli
agenti di un tipo sia pari al numero di essi moltiplicato per il rate del singolo.
1.4.4 Transizioni
Deniamo ora le transizioni:
T = (nome, guardia, update, rate)
πi := [guardia(X)− update(X)]@λ
guardia(X) è passabile ⇔SVi 0 ∧ Ask(G(X)) == true
update(X) = update(X)s CCP, SVi = SVi − 1, SVj = SVj + 1
In altri termini:
La guardia è espressa come la guardia di sCCP solo che presenta un vincolo
di positività sulla variabile di stato associata.
L'update è uguale all'update visto in sCCP ma aggiorna anche lo stato delle
State Variables. Vedendo un esempio:
A1 : −[X 0 → X = X − 1]@{k1}.A2 + [X 10 → X = 0]@{k2}.A1;
A2 : −[true → X = X + 1]@{k1}.A1;
SI : A1
15
27. Questo sistema, viene convertito in:
t1 = [SV 1 0 ∧ X 0− X = X − 1; SV 1 = SV 1 − 1; SV 2 =
SV 2 + 1]@{k1}
t2 = [SV 1 0 ∧ X 10− X = 0; ]@{k2}
t3 = [SV 2 0∧true− X = X +1; SV 2 = SV 2−1; SV 1 = SV 1+1]@{k1}
Nell'esempio t2 non tocca le SV perchè incrementerebbe e decrementereb-
be la stessa variable.
È evidente come una tale forma racchiude tutte le informazioni necessarie
per un algoritmo che voglia simulare l'andamento del sistema descritto dal
sorgente sCCP ridotto.
1.5 ODE
Con ODE si indicano le Equazioni Dierenziali Ordinarie ed è proprio da
queste che prende il nome il compilatore di tipo ODE. In questa sezione si
esporrà brevemente il collegamento tra ITS ed ODE. Verrà esposto come,
sotto opportune ipotesi, sia possibile passare da un sistema di tipo ITS ad
un sistema di equazioni dierenziali. Un approfondito studio dell'argomento
si può trovare in [5].
1.5.1 Deterministicità del Linguaggio
Questa semantica per sCCP non presenta evoluzione stocastica: l'evoluzione
temporale è determinata solo dalle equazioni dierenziali associate al sistema
scritto in forma di sCCP ridotto. Questo è un ulteriore vincolo sul sistema
stesso.
1.5.2 La denizione di ODE
Come visto per sCCP e per ITS, si è dovuto denire in termini formali il
sistema. Nello specico, assumiamo che le transizioni del sistema siano della
seguente forma:
τ ∈ T , τ = (guardτ (x, s), X = X + k, S = S + h, fτ (X, S))
k rappresenta l'aggiornamento delle variabili globali, il vettore h rappresenta
16
28. l'aggiornamento delle variabili di stato:
ki ∈ R, se i è aggiornata esplicitamente
k=
0, altrimenti
hi ∈ R, se i è aggiornata esplicitamente
h=
0, altrimenti
Si noti come richiediamo che gli update siano di una forma particolare, ov-
verro incrementi/decrementi delle variabili con quantità costanti. Da questa
denizione si ottiene il seguente sistema di ODE:
˙
X= τ ∈T kτ gτ (X, S)fτ (X, S)
˙
S= τ ∈T hτ gτ (X, Sfτ (X, S)
Dove X, S, h e k sono vettori.
In pratica, dal sorgente si deriva una denizione sintattica del sistema di
equazioni dierenziali. Questo ha delle relazioni con il sistema stocastico
associabile allo stesso sorgente, per una discussione su questa relazione si
rimanda a [5]. Un programma sCCP che soddis i vincoli di cui sopra, può
essere visto come una descrizione di alto livello di un sistema di ODE.
17
29. Capitolo 2
Analisi e Progettazione
In questo capitolo verranno analizzate le fasi di Analisi e di Progettazione
svolte per creare il progetto oggetto di questa tesi.
Nella prima sezione è stato analizzato il problema, partendo dai requisiti
funzionali.
Nella seconda sezione sono state progettate le componenti per rispondere ai
requisiti.
2.1 Analisi
Nel Capitolo 1 si sono viste le nozioni teoriche riguardanti sCCP e le due
varianti ITS e ODE.
Il progetto che si vuole creare, per l'appunto, consiste nell'implementazione
di sCCP nella maniera più versatile e più completa possibile, con un occhio
rivolto all'ecienza.
Il progetto è stato sviluppato in ambiente .NET, in particolare è stata uti-
lizzata la versione 4.0 del framework Microsoft. Le interfacce grache sono
state sviluppate in Windows Presentation Foundation (WPF).
2.1.1 Analisi dei Requisiti
È stato necessario interpellarsi sulle funzionalità necessarie al ne di creare
il framework. È facile immaginare che il requisito fondamentale sia quello di
comprendere codice di tipo sCCP: oltre a ciò è necessario che ne permetta
la simulazione in maniera integrata. Dato un codice, quindi, vogliamo ave-
re come riscontro dei dati sotto forma di graco o memorizzati in maniera
compatibile con la maggior parte dei sistemi di analisi dati. È stato deciso
18
30. inoltre di permettere all'utente di poter simulare passo passo per compren-
dere l'andamento non solo in chiave temporale, ma anche in chiave di step
esecutivi.
Si è voluto fornire anche un'interfaccia per la stesura del codice ma, nel con-
tempo, si è voluto far in modo che non fosse l'unica interfaccia possibile in
modo da permettere di accedere alle funzionalità del codice anche da altre
GUI scritte in futuro.
Un requisito fondamentale che è stato scelto è la creazione del Constraint
Store on demand: partendo da un sorgente contenente le informazioni per
indicare le proprie speciche, il framework dovrà essere in grado di modellare
un oggetto che le implementi nella maniera migliore.
L'interfaccia graca deve permettere di salvare e riprendere in seguito il la-
voro che si sta arontando. Oltre a questo deve permettere di impostare dei
criteri di arresto dell'esecuzione (raggiungimento temporale o raggiugimento
in passi).
Deve esistere un'interfaccia simulativa di tipo batch che può gestire grandi
carichi di lavoro in maniera eciente e gestire la simulazione con variazione
dei parametri iniziali automatica.
Oltre a ciò è necessario rendere il sistema in grado di utilizzare diversi tipi
di compilatori in modo da dare all'utente la possibilità di utilizzare sia ITS
che ODE.
Quanto detto si può riassumere nei seguenti punti:
• Il framework deve tradurre del codice scritto in sCCP, comprenderlo e
simularlo.
• Il framework deve fornire un'interfaccia all'utente in maniera da met-
tere a disposizione tutte le sue funzionalità.
• Il framework deve fornire un'interfaccia ad altri programmi in maniera
da mettere a disposizione tutte le sue funzionalità.
• Il framework deve generare un'output graco o numerico compatibile
con gli altri standard.
• Il framework deve essere capace di creare un Constraint Store specico
che risponda alle esigenze di ogni applicazione.
• Il framework deve poter gestire anche codice ITS e ODE.
2.1.2 Studio dei Casi d'Uso
Una volta analizzato il problema è stato denito il diagramma degli Use Case.
Si è visto, da quanto scritto sopra, che gli unici attori presenti sono:
19
31. • Utente
• Altri Software
Da quanto visto sopra, possiamo riassumere le attività che l'utente può fare
in:
• Scrivere il sorgente (salvare, caricare, compilarlo)
• Scegliere il compilatore
• Avviare la simulazione (graca o batch)
• Raccogliere i risultati
Mentre gli eventuali altri software dovranno poter:
• Scegliere il compilatore
• Avviare la simulazione (graca o batch)
• Raccogliere i risultati
Da questa prima analisi si arriva al graco in gura nella pagina successiva.
2.2 Progettazione
Ultimata la fase di analisi si è passati a quella di progettazione. In primis,
come illustrato nelle successivo sezioni, si è progettata la parte simulativa.
Nell'arontare l'argomento si è dovuto tener conto della possibilità di voler
scrivere altre IDE per i compilatori e di poter avere più compilatori per la
stessa IDE. Oltre a questo è stato necessario utilizzare un sistema standard
per l'output dei dati.
Per riuscire a mantenere il massimo distacco tra le componenti, si è deciso
di utilizzare delle interfacce per sottolineare i punti di contatto. In questo
modo se qualcuno dovesse volere riscrivere una parte del programma, non
deve far altro che reimplementare l'interfaccia associata.
È stato fondamentale dividere il codice sorgente dell'utente in due blocchi di-
stinti: una parte va a denire la congurazione degli agenti (compresi agenti
iniziali, denizioni agenti, ecc.) detta SourceAM, mentre l'altra serve a mo-
dellare il Constraint Store come l'utente desidera (variabili globali, funzioni,
constraints, parametri, operatori ... ) detta SourceCS.
20
32. Use Cases Diagram
2.2.1 Standardizzazione di Input ed Output
Si è fortemente cercata l'indipendenza delle parti su tutti i fronti, per questo
si è deciso di utilizzare degli standard già aermati per gestire input ed out-
put. Principalmente si doveva rendere compatibile con future espansioni e
varianti l'input (rappresentato per la maggior parte dai sorgenti) e l'output
(i dati di risposta della simulazione).
Per modellare l'input serviva un linguaggio in grado di esser modellato per le
nostre esigenze e che fosse di semplice lettura: per questo motivo si è scelto
XML. Essendo molto utilizzato, un domani chiunque potrebbe creare un'in-
terfaccia graca diversa che generi il codice XML da fornire al compilatore
senza grossi sforzi. Un domani uno sviluppatore potrebbe voler creare una
IDE completamente graca in cui l'utente non debba scrivere una sola linea
di codice: grazie all'utilizzo di XML non necessiterà di alcun parser o vali-
datore se non il DTD stesso dei le XML utilizzati.
Per la verica della correttezza sintattica dei le XML sono stati creati dei
21
33. les DTD apposta. L'utilizzo di questo sistema ha portato ad una sempli-
cazione anche nella creazione dei compilatori stessi: ogni compilatore avrà
un suo DTD per analizzare il codice così creato.
Per quanto concerne l'output si è scelto per un formato molto leggero ed
universale: il CSV (comma separated values). Questo formato è compatibile
con moltissimi programmi di analisi dati (come Mathlab o Excell) ed è prati-
camente un plain text e quindi leggibile anche con un qualunque text editor
(esattamente come XML).
2.2.2 I Macro Moduli
Per creare questo progetto è stato necessario utilizzare un approccio di tipo
Top-Down in cui si è iniziato immaginando dei macro blocchi che poi sono
andati a denirsi mano a mano che si scendeva nel dettaglio. Fin dal prin-
cipio si sapeva che dovevano esistere delle classi che assolvessero ai seguenti
compiti:
• Interfaccia Graca di Scrittura del Codice (Parser GUI)
• Parser da linguaggio a XML per il SourceCS
• Parser da linguaggio a XML per il SourceAM
• Interfaccia Graca per la simulazione graca in tempo reale
• Interfaccia Graca per la simulazione Batch
• Classi di scambio parametri di compilazione
• Compilatore
Con la congurazione esposta in gura un utente può utilizzare il progetto
in più modi:
• Utilizzare tutto il progetto, dal Parser GUI per la stesura al compilatore
per simulare
• Utilizzare un proprio Editor e passare il codice per la simulazione
Graca / Batch alle due classi predisposte
• Utilizzare solo il compilatore lasciando poi il proprio software a gestire
i dati in input e output
22
34. Figura 2.1: Primo Approccio Progettuale
2.2.3 Il Compilatore
Il Compilatore in questo progetto non è unico e non perchè si è scelto di
crearne più del dovuto, ma perchè ogni Compilatore deve comprendere un
particolare tipo di codice.
Avendo di base due tipi di codice, uno scritto per la denizione degli agenti
e l'altro per la denizione del Constraint Store, è evidente che ci dovranno
essere un modulo per comprendere l'uno e un per comprendere l'altro. Avre-
mo quindi, in generale, una suddivisione tra Parser per SourceAM e uno per
SourceCS.
È comprensibile come un codice scritto per il Parser X non necessariamente
debba funzionare per il Parser Y, ma la divisione in queste due categorie
serve per avere dei risultati comuni.
Ad esempio da un Parser per il SourceCS (ParserCS) ritornerà sempre un
contenitore con variabili, funzioni, constraint e quant'altro; allo stesso modo
un Parser per il SourceAM (ParserAM) ritornerà sempre un elenco di deni-
zioni di Agenti ed uno stato iniziale.
Avendo scelto di utilizzare in ingresso dei codici XML, i Parser non hanno
bisogno di librerie particolari per produrre il risultato ma gli basta saper
analizzare un le XML.
Va rispettato un vincolo: ParserCS deve eseguire sempre prima di Parse-
rAM. La motivazione è molto semplice: ParserAM ha bisogno di sapere chi
sono le variabili, chi i constraint, chi le funzioni e quant'altro per poter fare
23
35. correttamente il proprio lavoro.
ParserAM conosce in questo modo i dati per la denizione del nuovo Con-
straint Store; oltre a questo gli vengono forniti gli indirizzi dei RuntimeMa-
nager appena istanziati in modo da dare loro i risultati da esso prodotti.
2.2.4 Runtime Manager
Come ParserAM e ParserCS svolgono le funzioni di comprensione del sor-
gente, il Runtime Manager svolge quelle di simulatore. Esso infatti conosce,
grazie a ParserAM, tutte le denizioni sia in termini di Agenti che del Con-
straint Store.
Viene istanziato fornendogli semplicemente i criteri di arresto (durata tem-
porale λ o numero di passi). Un Runtime Manager espone (principalmente)
le seguenti funzioni:
• Start - Inizia la simulazione
• Stop - Interrompe la simulazione
• Step - Eettua un passo della simulazione
• GetGraphData - Fornisce tutti i dati relativi alle variabili ed al loro
andamento temporale
• GetGlobalVars - Fornisce il valore attuale della variabili globali
• GetStatus - Fornisce un riassunto di Agenti Attivi, ultime azioni ese-
guite, rate, ecc.
Come è evidente il Runtime Manager è in tutto e per tutto il cuore del pro-
getto: tutte le altre parti non sono che moduli che permettono l'interazione
con esso.
Una volta dato il comando Start, questo simulerà nchè non gli viene dato
lo Stop o soddisfa le condizioni di terminazione.
Analizzando il linguaggio sCCP ci si è resi conto che un simulatore ben
strutturato deve esser dotato di due elementi:
• Il Constraint Store
• Il Denition Store
24
36. 2.2.5 Constraint Store
Il Constraint Store (CS) sarà la memoria del nostro sistema. In esso ci
saranno le variabili e vi si accederà tramite gli Ask e i Tell. Oltre a ciò
dobbiamo poter richiedere al CS di ritornare il risultato di una formula:
questo viene usato per il calcolo dei Rate. Un'altra funzionalità è quella di
ritornare lo stato di tutte le variabili in un operazione sola. Al CS viene dato
anche il compito di modicare, se necessario, le formule lette dai Parser.
Ricapitolando:
• Ask
• Tell
• GetRate
• GetVariables
• Parse
2.2.6 Denition Store
Il Denition Store è il gestore delle denizioni di agenti. Deve aiutare la
fase di Parsing del SourceAM e deve fornire al RuntimeManager gli agenti
richiesti. È unico per ogni Runtime Manager. I requisiti che deve soddisfare
sono:
• Creare una denizione
• Creare un'azione per una denizione
• Fornire le istanze delle denizioni
Vista la denizione degli agenti, si è deciso di progettare la singola denizione
partendo dalle seguenti considerazioni:
• Ogni Agente ha più scelte
• Ogni scelta ha una o più azioni
• Ogni azione può essere composta da una o più azioni atomiche
Per gestire questa condizione si è pensato al seguente sistema. Ogni Agente
ha una scelta tra le possibili azioni. Ogni serie di azioni composte in sequenza
sono gestite da un Action Manager. Le azioni implementano tutte la stessa
interfaccia che espone molti metodi, tra cui quello per l'esecuzione dell'azione
e quello per ricevere il Rate dell'azione stessa.
Vediamo ora un esempio sulla gestione dell'azione:
25
37. Agente :- [X 0 - X = X-1]@{1}.Agente
+ [X == 0 - X = 10]@{1}.([ - Y= Y + 1]@{1}.Agente || Agente + Agente);
Questa struttura viene dapprima spezzata in due:
[X 0 - X = X-1]@{1}.Agente
[X == 0 - X = 10]@{1}.([ - Y= Y + 1]@{1}. Agente || Agente + Agente);
La prima azione ora diventa:
ActionManager - [Guardia][Call]
La seconda invece diventa:
ActionManager - [Guardia][Scelta]
[Scelta] - ActionManager - [Guardia][Parallelo([Call][Call])
- ActionManager - [Call]
In pratica un ActionManager è una lista di Azioni collegate dalla Composi-
zione in Sequenza: quando il Parser individua una scelta aggiunge in coda
un'elemento Scelta, il quale avrà i suoi ActionManager per gestire le biforca-
zioni.
In questo modo si gestiscono le azioni come fossero un albero, in maniera
eciente e assolutamente non rigida.
In gura un diagramma approssimato ma riassuntivo.
2.2.7 Interfaccia Graca
È stato fondamentale progettare da subito anche l'interfaccia graca: grazie
ad un'approfondita analisi delle funzionalità che si volevano esporre si è riu-
sciti ad apportare modiche anche ai diagrammi delle classi che formano il
Core.
Per prima cosa è il caso di dividere l'interfaccia graca in tre blocchi distinti:
• GUI per la scrittura del codice
• GUI per la simulazione in tempo reale
• GUI per la simulazione Batch
26
38. Figura 2.2: Rappresentazione concettuale delle classi
Parser GUI
Questa WPF Window è stata progettata per essere il più semplice possibile.
Divisa in due parti in cui scrivere il SourceAM e il SourceCS, ore le funzioni
di base (salva, carica, nuovo, esci) aancate dalla possibilità di compilare
(AM, CS, Entrambi), di scegliere il compilatore (sCCP, ITS, ODE) e inne
la possibilità di lanciare la simulazione (Graca o Batch).
Nel capitolo 3 vedremo un esempio pratico con tanto di immagini prese dal
programma.
Graphic Simulation GUI
Questa interfaccia si compone di tre nestre distinte che vengono mostrate
una di seguito all'altra.
Nella prima si è progettato che venga data la possibilità di scegliere i para-
metri di arresto, i le XML da caricare (SourceAM e SourceCS, impostati
dal Parser GUI con i giusti valori), la precisione del graco e il numero di
simulazioni parallele.
Nella seconda nestra è stata data la possibilità di scegliere i dettagli del
graco: si possono scegliere le variabili da analizzare mostrandole tracciate,
di quali variabili mostrare la media, di quali la varianza. Per ogni linea da
tracciare si può scegliere il colore e la didascalia.
Avendo concentrato le scelte prima dell'esecuzione, l'ultima nestra è quasi
27
39. completamente dedicata al graco. Si sono volute mostrare anche le variabili
con i loro valori e gli agenti in vita, ma la maggior parte dello schermo è presa
dal graco. Questa nestra si è pensato debba avere funzionalità interattive
(permettndo di iniziare o fermare la simulazione o di fare un singolo passo),
permettendo di salvare e ricaricare lo stato ed anche di esportare un le .CSV
contente i dati raccolti.
Batch Simulation GUI
La simulazione batch deve raccogliere i seguenti dati:
• Scelta criteri di arresto
• Scelta numero di esecuzioni
• Scelta le da eseguire
• Scelta le da salvare
• Scelta delle variazioni delle variabili in ingresso
Come nel caso precedente si è pensato di procedere con tre schemate per
poter scegliere le opzioni. Nella prima si possono così concentrare le infor-
mazioni in merito ai criteri di arresto, le di eseguire, le da salvare e numero
di esecuzioni.
Nella seconda così ci si può concentrare sulle variazioni dello stato iniziale:
per ogni variabile ed ogni stato si devono poter impostare dei possibili valori
iniziali e il sistema deve poi valutare le esecuzioni sugli opportuni valori ini-
ziali.
A questo punto nella terza videata basta predisporre un indicatore di com-
pletamento e qualche controllo sulla durata delle operazioni (per evitare
loop).
28
40. Capitolo 3
Interfaccia
In questo capitolo tratteremo l'utilizzo del framework in oggetto di questo
elaborato.
Nella prima parte analizzeremo le procedure per l'installazione.
Nella seconda discuteremo in merito alla stesura del codice, analizzando in
dettaglio come stendere un semplice codice d'esempio.
Nella terza sezione vedremo come simulare in forma graca e non.
3.1 Installazione
L'installazione del progetto risulta molto semplice. Una volta scaricato il
le compresso del progetto, basta scompattare la cartella. A questo punto è
possibile eseguire il le sCCP-GUI.exe ed utilizzare il programma.
Se il programma non dovesse funzionare, è probabile che il computer sul qua-
le si sta lavorando non disponga del .NET Framework 4.0.
Per l'instazzazione di quest'ultimo, basta visitare il sito di Microsoft e scari-
care il software in questione.
3.2 Il Codice
Una volta lanciato l'eseguibile sCCP-GUI.exe, apparirà un'interfaccia gra-
ca per la stesura del codice. Oltre ai soliti comandi che si trovano in una
usuale IDE (salva, carica, esci, nuovo, compila) ne sono stati aggiunti altri
per lanciare la simulazione di tipo Batch e di tipo Graco.
Il corpo della nestra principale è diviso in due aree di testo: in quella di
sinistra verrà scritto il Source AM, mentre in quella di destra il SourceCS.
Il menù Compile permette di scegliere di compilare il SourceAM, il Sour-
ceCS o entrambi.
29
41. Nel menù Engine è possibile scegliere il simulatore tra i tre visti nelle
precedenti sezioni:
• sCCP
• ITS
• ODE
Se si dovesse scegliere ODE, verrà data la possibilità di scegliere quale meto-
do utilizzare per risolvere il problema (Implicit Runge Kutta...). Oltre a ciò
è possibile scegliere la precisione del solver. Nel menù Constraint Store è
possibile scegliere se utilizzare il Constraint Store basato su muParser oppure
quello basato su Flee.
Se si volesse scavalcare la l'interfaccia graca per la stesura del codice, è possi-
bile lanciare i simulatori direttamente. Questi si trovano nella stessa cartella,
con il nome sCCP-GraphicSimulator.exe e sCCP-BatchSimulator.exe. Nel-
l'ultima sezione di questo capitolo verrà spiegato l'utilizzo di questi e come
interfacciarli ad eventuali altre IDE.
3.2.1 Il Codice SourceAM
In questa sezione si daranno alcune informazioni sul come scrivere il codice
SourceAM. Per prima cosa dobbiamo ricordare che a seconda del compila-
tore il linguaggio subisce delle restrizioni dovute alle limitazioni teoriche. In
questa sezione parleremo, in modo del tutto generale del codice sCCP.
Il SourceAM è costruito in questo modo:
Agent_Definitions
/* Definitions */
End_Agent_Definitions
Initial_State
/* Initial State */
End_Initial_State
All'interno dello spazio dedicato alle denizioni, possono venir scritti agenti
in questo modo:
NomeAgente(Variabili) :- Azione (.Azione)+
Variabili = Nome | Nome = Valore
Azione = Guardia | Chiamata | Parallelo | Scelta
Guardia = [Ask - Tell]@{Rate}
Chiamata = NomeAgente(Variabili)
Parallelo = Azione || Azione ...
Scelta = Azione + Azione ...
30
42. Per la compatibilità delle istruzioni assegnate in Ask, Tell o Rate si rimanda
al Capitolo 4. Un Tell può esser costituito da più chiamate al Constraint
Store; queste devono essere separate da un ;. Se il codice viene compila-
to da sCCP è possibile assegnare dei valori iniziali alle variabili locali così
denite, altrimenti rimangono dei semplici segnaposto: nel momento in cui
verrà chiamato l'Agente fornendogli dei riferimenti in ingresso, questo verrà
modicato per inserire la nuova variabile al posto del segnaposto.
In sCCP è previsto l'operatore di dereferenziazione, come in C.
Ad ogni assegnazione, se viene passata una variabile in una chiamata ricor-
siva, questa viene passata per riferimento, mentre se si vuole passare solo il
valore si deve utilizzare l'operatore *.
Esemplicando:
Agente(X):- [X 0 - X = X-1]@{k1}.Agente(X);
Agente2 :- Agente(Y);
Nel momento in cui viene invocato Agente(Y), il segnaposto viene sostituito:
Agente(Y):- [Y 0 - Y = Y-1]@{k1}.Agente(Y);
Se invece l'istruzione fosse stata:
Agente2 :- Agente(*Y);
Agente(X) sarebbe rimasto uguale ma sarebbe stato assegnato ad X il valore
delle variabile attuale della variabile Y.
Questo meccanismo è utilissimo per specicare lo Stato Iniziale, in quanto
permette di modellare degli agenti con lo stesso comportamento su variabili
diverse.
Lo Stato Iniziale è così composto.
Initial_State
/* Agenti */
End_Initial_State
Agenti = Numero * Chiamata || Chiamata
Chiamata = NomiAgente || NomiAgente(Variabili)
Un esempio di inizializzazione, facendo riferimento ad Agente(X) visto prima
può essere il seguente:
Initial_State
Agente || Agente(Y) || 2 * Agente(Z)
End_Initial_State
31
43. 3.2.2 Il Codice SourceCS
In questa sezione si vedrà come scrivere il SourceCS per modellare il Con-
straint Store con il quale interagiranno gli agenti del SourceAM.
Il sorgente deve dar la possibilità di denire:
• Variabili
• Parametri
• Costanti
• Operatori
• Funzioni
• Constraints
Per ogni elemento esiste una sintassi per la sue generazione. Il SourceCS ha
questa forma:
Global_Var
/* Var Defs */
End_Global_Var
Params
/* Param Defs */
End_Params
Functions_Def
/*Function Defs */
End_Functions_Def
Constraints_Def
/* Constraint Defs */
End_Constraints_Def
Var Defs := NomeVariabile = Valore ;
Param Defs := param NomeParametro Valore ;
Function Defs := Constant OR Function OR Operator
Constant := CONST costante valore ;
Function := Nome(Vars) = Semantica ;
Operator := operat Simbolo/i Semantica ;
Constraint Defs := Nome(Vars) = (Var = Semantica; Var = Semantica ...);
Ogni elemento dichiarato all'interno del SourceCS è richiamabile negli Ask,
nei Tell e nei Rate del SourceAM. Nel capitolo 5 sono illustrati alcuni esempi
completi di programmi funzionanti. Si rimanda il lettore a quella sezione per
vedere in pratica le denizioni viste in questa e nella precedente sezione.
32
44. 3.3 La Simulazione
In questa sezione si analizzeranno sCCP-GraphicSimulator e sCCP-BatchSimulator.
Entrambi possono venir eseguiti come programmi stand alone e vengono ese-
guiti dalla stessa sCCP-GUI quando si inviano i comandi di simulazione. Per
simulare dalla sCCP-GUI si deve utilizzare il Menu Run, il quale permette
di scegliere tra Graphic Simulation e Batch Simulation.
Una volta mandata in esecuzione da sCCP-GUI, è come se questa non esi-
stesse più e ci si trova a contatto diretto con il simulatore.
3.3.1 Graphic Simulator
Il Graphic Simulator è composto da tre videate, ognuna con uno specico
utilizzo. Queste vengono mostrate in ordine e l'ultima è quella che mostra la
simulazione vera e propria. Queste servono a:
1. Scelta dei criteri di arresto, scelta sorgenti e numero di simulazioni
2. Scelta delle variabili da monitorare, dei colori delle linee etc
3. Simulazione e graci
Prima Schermata
Nella prima schermata sono impostabili i criteri di arresto. Questi possono
essere di due tipi: sul numero di passi o sul tempo di sistema. Una volta scelto
il criterio di arresto si può passare alla scelta della precisione dell'analisi. Se
si è scelto di concludere a un dato tempo, si può scegliere di campionare N
punti (decretanto così la precisione dell'output) oppure si può scegliere un
passo temporale di campionamento ed utilizzare direttamente quello.
Nel caso la scelta ricada su un criterio di terminazione legato al numero di
passi, si può scegliere ogni quanti passi campionare.
Oltre a questo si possono scegliere altre cose:
• Il numero di simulazioni
• Il le XML che descrive il Constraint Store
• Il le XML che descrive l'Agent Manager
• Se mostrare il graco durante la simulazione o alla ne
Nel caso il simulatore venga caricato dalla sCCP-GUI, i campi contenenti i
percorsi dei les vengono già assegnati con i nomi corretti.
33
45. Seconda Schermata
Nella seconda schermata l'utente può impostare le impostazioni riguardanti il
graco. Nella lista superiore vengono indicate le variabili globali del progetto.
Una volta selezionata, l'utente può scegliere se:
• Stampare i singoli valori della variabile delle diverse simulazioni
• Stampare la media dei valori della variabile derivanti dalle diverse
simulazioni
• Stampare la varianza dei valori della variabile derivanti delle diverse
simulazioni
Una volta cliccato su uno dei tre pulsanti, viene aggiunta una nuova riga
nella lista dei valori da stampare. In questa lista è possibile congurare più
parametri:
• Il nome da mostrare nella legenda del graco
• Il colore della linea
• La sorgente dei dati
La sorgente dei dati serve per selezionare di quale simulazione si vuole tener
traccia. Ad esempio è possibile calcolare la media tutte le simulazioni eccetto
una e di questa mostrare il valore singolo. In questo modo si può comparare,
per ni l'andamento medio con l'andamento di una singola simulazione.
Una volta selezionati i valori, si può premere sul tasto OK per passare alla
terza schermata.
Terza Schermata
La terza schemata è stata progettata per interfacciarsi con il RuntimeMa-
nager dando all'utente tutte le informazioni di cui ha bisogno. Buona parte
dello schermo è coperto dal graco. Sulla destra vengono riportati i valori
delle variabili e gli agenti vivi. In basso a sinistra una label riassume lo sta-
to del Runtime Manager (ultima azione eseguita se si va per step, rate del
sistema etc).
I Menù permettono di di interagire con il sistema:
File:
• New - Crea una nuova simulazione copia di quella in esecuzione
• Load Status - Carica una simulazione precedentemente salvata
34
46. • Save Status - Salva una simulazione
• Save CSV - Salva i dati su disco in Comma Separated Values
• Close - Chiude la simulazione
Run:
• Single Step - Esegue un passo nella simulazione
• Start/Stop - Avvia/Interrompe la simulazione
3.3.2 Batch Simulator
Il Batch Simulator è strutturato in 3 parti, come il Graphic Simulator. Le
tre nestre hanno scopi diversi rispetto a quanto precedentemente visto:
1. Scelta criteri di arresto e precisione dei dati, scelta della locazione dei
CSV con i risultati e dei le XML da compilare
2. Scelta delle diverse congurazioni di valori iniziali e parametri nelle
diverse simulazioni
3. Semplice indicatore dell'avanzamento del lavoro
Prima Schermata
La prima nestra è stata impostata allo stesso modo della prima del Graphic
Simulator. I criteri di arresto vengono introdotti nello stesso modo. Nel-
l'interfaccia è sostituita la CheckBox che prima serviva la visualizzazione del
graco a run-time o alla ne della simulazione; questa ora serve per stabilire
se l'utente vuole il risultato salvato in un unico le o in uno diverso per ogni
simulazione. Oltre a questo una TextBox ore la possibilità di scegliere dove
salvare l'output prodotto.
Seconda Schermata
La seconda schermata permette all'utente di scegliere diverse congurazioni
di valori delle variabili e dei parametri. Questa schermata serve, ad esempio,
a vedere come varia il comportamento del sistema mano a mano che un pa-
rametro cresce. Da una ComboBox si può scegliere il valore da modicare,
una volta aggiunto si può scegliere come farlo variare. È possibile scegliere
che la variabile acquisica N valori uniformemente distribuiti su un'intervallo,
oppure che il valore di questa venga incrementato / decrementato ntanto
35
47. che rimane all'interno di un intervallo.
Il numero di simulazioni sarà proporzionale al numero di combinazioni pos-
sibili. Se ad esempio si fa variare la prima variabile su 3 valori e un'altra su
2, ci saranno 6 simulazioni.
Terza Schermata
La terza schermata è assolutamente stringata: presenta solo una barra di
avanzamento dei processi. Quando le simulazioni vengono completate, que-
sta avanza. Quando la simulazione è conclusa, viene riportato il tempo
impiegato, l'istante di inizio elaborazione e di ne elaborazione.
36
48. Capitolo 4
Implementazione
Dopo aver esposto le nozioni teoriche nel capitolo 1, l'analisi e la progettazio-
ne nel capitolo 2 e aver mostrato l'interfaccia nel capitolo 3, in questo quarto
capitolo si presenterà l'implementazione.
Nella prima sezione verranno analizzate velocemente le schermate. Nelle
successive si analizzeranno a turno i diversi compilatori.
4.1 Dettagli dell'Interfaccia Graca
Per la programmazione dell'interfaccia graca sono stati utilizzate delle com-
ponenti esterne, non scritte dal candidato. Nello specico si sono usate le
seguenti librerie:
• ColorPicker
• DynamicDataDisplay
La prima consentiva di prelevare un colore da una tavolozza ed è stata inseri-
ta nella scelta dei colori nella simulazione graca. Sempre nella simulazione
graca si è utilizzato il DynamicDataDisplay. Questi altro non è se non un
controllo WPF che gestisce dei graci a linea.
Quando viene istanziato la nestra con il graco vengono letti i parametri e
desunte le linee da disegnare. A questo punto vengono memorizzate in un
dizionario, per ogni linea, una lista di ObservablePoint. Ogni volta che verrà
aggiunto un punto in una di queste liste, questo verrà tracciato nella corretta
linea.
Quando viene lanciata una simulazione, viene anche attivato un thread che
va a controllare ogni 500ms se vi sono nuovi dati disponibili dal Runtime-
Manager. Questi vengono convertiti in modo da essere utilizzabili e vengono
37
49. inseriti nelle liste di punti opportune.
Nel gestire simulazioni parellele contemporanee, onde evitare problemi con
calcoli della media e inconsistenza dei dati, si è deciso di disegnare i punti no
all'istante temporale del RuntimeManager di tempo minimo. Ad esempio, si
considerino 4 simulazioni che dopo 500ms si trovano nel seguente stato:
Sim1 : T = 35 Sim2 : T = 50 Sim3 : T = 80 Sim4 : T = 12
A questo punto il simulatore vede che Sim4 è la simulazione più in ritardo e
disegna sul graco tutti i punti richiesti no al tempo 12.
Nel caso una delle simulazioni dovesse incontrare un blocco dovuto al codice
(rate pari a 0), non sarà possibile continuare la tracciatura del graco.
L'interfaccia graca si appoggia su un OutputManager che gli permette di
memorizzare su disco lo stato corrente, i risultati delle elaborazioni e di
ricaricarli all'occorrenza.
4.2 SCCP
SCCP è il primo compilatore e simulatore che è stato scritto per questo pro-
getto. È quello che presenta maggiori requisiti da implementare in quanto
deve poter simulare tutte le caratteristiche del linguaggio SCCP. Nelle pros-
sime sezioni vedremo nel dettaglio come sono state implementate le diverse
componenti che lo formano.
Inizieremo esaminando il Constraint Store, parleremo poi del Denition Sto-
re, del Runtime Manager e inne dei Parser (ParserCS a ParserAM).
4.2.1 Il Constraint Store
Nella sezione 1 abbiamo appreso come il Constraint Store debba rispondere
ad Ask e Tell, mentre nella fase di progettazione ci si è resi conti che le
funzionalità andavano ampliate. Nello specico si è giunti a:
• Ask
• Tell
• GetRate
• Parse
• Metodi per istanziare variabili, parametri, funzioni, operatori, con-
straint e quant'altro.
38
50. • GetVariables
È stato necessario appoggiarsi ad un parser di espressioni matematiche per
gestire il Constraint Store. La scelta è ricaduta su muParser (modulo esterno
non scritto dal tesista).
muParser
MuParser è reperibile all'indirizzo muparser.sourceforge.net.
È gratuito e disponibile in forma nativa per C++ e Wrappato per ambiente
.NET.
Questa libreria permette di calcolare il valore di espressioni contenenti nu-
merosi operatori e dà anche la possibilità di espandere le proprie potenzialità
tramite opportuni metodi. Oltre a ciò permette di memorizzare al proprio
interno parametri e variabili. È uno dei migliori parser matematici gratuiti
a livello di performances.
In questa prima tabella vengono esposte le funzioni built-in in muParser.
39
51. Name ARC Explanation
sin 1 sine function
cos 1 cosine function
tan 1 tangens function
asin 1 arcus sine function
acos 1 arcus cosine function
atan 1 arcus tangens function
sinh 1 hyperbolic sine function
cosh 1 hyperbolic cosine
tanh 1 hyperbolic tangens function
asinh 1 hyperbolic arcus sine function
acosh 1 hyperbolic arcus tangens function
atanh 1 hyperbolic arcur tangens function
log2 1 logarithm to the base 2
log10 1 logarithm to the base 10
log 1 logarithm to the base 10
ln 1 logarithm to base e (2.71828...)
exp 1 e raised to the power of x
sqrt 1 square root of a value
sign 1 sign function -1 if x0; 1 if x0
rint 1 round to nearest integer
abs 1 absolute value
min var. min of all arguments
max var. max of all arguments
sum var. sum of all arguments
avg var. mean value of all arguments
40
52. Operator Meaning Priority
= assignement -1
and logical and 1
or logical or 1
xor logical xor 1
= less or equal 4
= greater or equal 4
!= not equal 4
== equal 4
greater than 4
less than 4
+ addition 5
- subtraction 5
multiplication 6
/ division 6
^ raise x to the power of y 7
?: if then else operator 1
In questa tabella vengono esposti gli operatori built-in in muParser.
Interfacciamento con muParser
Come visto, muParser ha delle peculiarità molto importanti per quanto ri-
guarda la denizione di funzioni, variabili e quant'altro. Per quanto riguarda
Ask, Tell e GetRate la loro denizione diventa banale: basta che il Constraint
Store faccia da Wrapper, facendo attenzione a casi particolari. Ad esempio
il parsing di un'istruzione vuota genera un'eccezione, ma si è deciso invece
che l'Ask di una tale istruzione sia sempre valido, quindi si è dovuta gestire
l'eccezione e ritornare il valore true.
La denizioni di variabili e parametri è molto semplice, così come l'ottenere
il valore delle variabili in muParser; basta anche in questo caso fare poco più
che un Wrapper. Gli argomenti più spinosi rimangono: la denizione di User
Dened Funcion (UDF) e il Parsing delle stringhe per i Parser.
Per risolvere il problema delle UDF è stato escogitato un metodo molto mal-
leabile che permette di gestire funzioni molto complicate e con molti para-
metri. Per denire una nuova funzione in muParser, questo non si aspetta
altro se non un delegato da invocare passando i parametri quando la incontra
durante il parsing.
Per creare a runtime una funzione che risolva questo problema si sono usate
opportune classi neutre. Viene denita una classe Function che si aspetta in
ingresso una lista parametri e la semantica della stessa. I parametri non han-
no un valore, ma solo il nome del segnaposto nella semantica. Questa classe
41
53. prevede un metodo Solve che accetta come parametri un array di double e un
intero contenente il numero di parametri. Questa rma è esattamente quella
che si aspetta muParser per creare una nuova funzione.
Associato il nome alla funzione Solve della classe appena generata, il Parser
la invocherà al momento opportuno.
Quando questa viene invocata, va a sostituire tutti i parametri che riceve
in ingresso (dei double) ai segnaposti indicati prima come parametri (delle
string). A questo punto la stringa può venir risolta da muParser in maniera
ricorsiva risolvendo il problema.
Vediamo ora un semplice esempio:
F(X,Y,Z) = (X + Y) / Z;
Il Parser la spezza in modo da ottenere una lista di stringhe contente [X,Y,Z]
e la semantica (X+Y) / Z. A questo punto crea la Function passando i valori.
Nel momento della chiamata:
F(1,3,5)
Invoke Solve([1,3,5],3)
Sostituzione Parametri ai segnaposto: (X + Y) / Z ⇒ (1 + 3) / 5
return Parser.Solve((1 + 3) / 5);
Da questo pseudocodice si comprende subito la linearità dei passaggi e la
correttezza degli stessi.
Un problema altrettando spinoso da trattare per il Constraint Store è il Par-
sing per il ParserAM. Questo serve per rendere compatibili le stringhe che
poi verranno passate a muParser.
Un esempio banale è dato dalla gestione delle variabili globali: per distingue-
re le une dalle altre, alle variabili globali viene applicato il presso GLOBAL.
Se non viene fatto un parsing corretto, un Agente che avesse un Ask del tipo
[X 0] creerebbe un'eccezione per variabile non trovata. Il Constraint Store
deve, per l'appunto, controllare le formule e modicarle:
[X 0] ⇒ [GLOBALX 0]
Il Constraint è una particolare UDF che serve ad eettuare molteplici opera-
zioni parametrizzate. Un esempio di Constraint potrebbe essere il seguente:
CS(ix, ipsilon) = ( X = ix/2; ipsilon = Y+2;);
Il seguente constraint pone ad X il valore di ix /2 (qualunque sia il valore di
ix) e modica il valore di ipsilon ponendolo a Y + 2.
42
54. È facile comprendere il caos che si può creare durante questa conversione.
È stato necessario convertire le stringe in maniera che ogni chiamata diven-
tasse diversa dalle altre: è da ricordare infatti che le funzioni denite in
muParser accettano solo parametri double e non stringhe.
La soluzione che si è adottata prevede di parsare le stringhe associando un
diverso constraint per ogni sua invocazione, così da poter modicare la se-
mantica a seconda della chiamata.
Si consideri ad esempio il constraint CS prima denito e si analizzi la chia-
mata:
[ - CS(K,MyCount)]@{...
È necessario creare il constraint partendo dalla denizione e tenendo conto
di:
• Variabili Globali (se X è globale diventa GLOBALX)
• Nome del Constraint Store (deve essere diverso per ogni invocazione)
• Sostituire in tutte le istruzioni il nome della variabile presente nella
chiamata.
Tenendo conto di queste particolari condizioni, il Constraint Store produrrà
in risposta alla richiesta di parsing:
CS1(GLOBALK, MyCount)
Dove in muParser è denita una funzione che esegue:
GLOBALX = GLOBALK / 2
MyCount = GLOBALY + 2
Per quanto concerne la creazione degli operatori deniti dall'utente, muParser
gestisce il tutto in maniera simile.
La sintassi denita è come:
operat SIMBOLO (elemento1, elemento2, precedenza)
Ad esempio:
operat ^= (p1, p2 ,1) = (p1 - p2) /2;
Questo operatore, ad esempio fa la media dei due elementi. La gestione in
muParser è simile, l'unica dierenza è la precedenza dell'operatore, indicata
dal terzo parametro. È possibile capirne il signicato guardando la tabella
con i built-in operators. Dopo aver costruito il Constraint Store basato su
muParser si è riscontrata una mancanza di performance dovuta alla non
compilazione delle formule. Per questo motivo si è creato un altro Constraint
Store basato su un altra libreria: FLEE.
43
55. FLEE
FLEE(Fast Lightweight Expression Evaluator) è un valutatore di espressioni
molto veloce dalle funzionalità ridotte. La libreria è gratuita e disponibile su
ee.codeplex.com.
Vediamo ora i vantaggi di questa libreria:
• Compila le formule, fornendo delle prestazioni molto maggiori
• Si interfaccia facilmente con altre librerie per aggiungere funzionalità
• Supporta la chiamata di funzioni di sistema e codice IL
Flee presenta anche dei notevoli svantaggi:
• Non permette l'assegnazione come elemento delle formule (X = X+1
risponde false)
• Non permette la creazione di parametri o costanti
• Non permette di creare degli operatori custom
A questo punto si è deciso di creare un Constraint Store limitato ma decisa-
mente più veloce. In questo non sarà possibile:
• Denire Constraints
• Denire Operatori custom
Se per un particolare programma non servono queste due funzionalità è pos-
sibile utilizzare questo Constraint Store che, come sarà esposto nel quinto
capitolo, ha delle performance di gran lunga superiori. La denizione del-
le funzioni è stata implementata in una maniera simile a quanto visto per
muParser. In questo caso si è creato un Function Manager che espone una
funzione F che accetta come parametro una stringa. Il Constraint Store mo-
dicherà tutte le chiamate alle funzioni che trova in questo modo:
miaF(X,2,K) → F(miaF,X,2,K)
Quando FLEE incontrerà F, invocherà la funzione del Function Manager.
Questi spezzerà la stringa usando come divisore le virgole. Al primo posto
trova il nome della funzione da evocare e la evocherà dall'elendo di funzioni
denite.
I Tell sarebbero inutili se impossibilitati a modicare il sistema. Si è quin-
di intervenuti per rendere possibile la modica delle variabili. Quando il
44
56. Constraint Store riceve un Tell, verica se la stringa è formata da nome va-
riabile seguita dal simbolo =. Se così è allora viene risolta la parte successiva
all'uguale e il risultato viene assegnato alla variabile. Nel caso la variabile
appartenga alla lista di parametri fornita dal SourceCS, l'assegnazione non
viene fatta, trattando di fatto la variabile come una costante.
Ora verranno brevemente esposte le funzionalità di Flee:
Operazioni Aritmetiche Flee permette di eseguire tutte le operazioni
aritmetiche come somme, prodotti, elevamenti a potenza e moduli.
ES: X * 2 + B ^ 100 % 10
Operazioni di Confronto Le operazioni di confronto ammesse sono tutte
le più comuni. Per indicare il diverso si utilizza l'operatore , mentre per
vericare l'uguaglianza di due oggetti si può usare semplicemente l'uguale.
ES: (X Y)
Operazioni di And/Or/Xor/Not Essendo ogni operazione in Flee for-
temente tipizzata, l'utilizzo di questi operatori varia a seconda del contesto.
In alcuni casi Flee utilizzerà l'And logico, mentre in altri farà quello Bit a
Bit per esempio.
ES (logico): a 100 And Not b = 100
ES (bit a bit): (100 or 2) and 1
Operazioni di Shift Grazie agli operatori e è possibile fare degli Shift
dei valori.
ES: 100 2
Operatori di Contatenazione Come in molti liguaggi di programmazio-
ne moderni, il + in Flee sta ad indicare la concatenazione tra stringhe.
ES: abc + def
ES: the number is: + 100
Operatori di Indicizzazione Con Flee si possono modellare ed utilizzare
Array dentro le formule. ES: arr[i + 1] + 100
Scrittura dei Simboli In Flee ogni valore viene interpretato in un parti-
colar modo per tipizzarlo nella maniera corretta:
• Char - Un carattere tra singoli apici 'a'
45
57. • Boolean - true o false
• Real - Ogni numero con un punto decimale. Con 'd' ed 'f ' si può
specicare la precisione (oat, double)
• Integral - Ogni numero intero. Con 'U' lo si specica Unsigned, mentre
con 'L' lo si considera a 64 bit
• Hex - Numero esadecimale, espresso nella notazione: 0xFF12
• String - Insieme di caratteri racchiuso tra due doppi apici
• Null - Alla variabile viene assegnato il valore null
• DateTime - Una data scritta come: #08/06/2008#
Casting Essendo tutte le variabili fortemente tipizzate (e non tutte double
come in muParser), la conversione si fa tramite cast.
ES: 100 + cast(obj, int)
Operatori Condizionali Come visto in muParser, anche quì è presente
un operatore condizionale.
ES: If(a 100 and b 10, both greater, less)
Operatore In Questo operatore è un operatore booleano che ritorna l'ap-
partenenza di un valore o meno ad un insieme. Lo si può utilizzare con un
valore ed una lista di valori dello stesso tipo.
ES (List): If(100 in (100, 200, 300, -1), in, not in)
ES (Collection): if(100 in collection, in, not in)
Le funzioni messe a disposizione da Flee sono le stesse disponibili nella libreria
Math di .NET. Grazie alla sua essibilità, infatti, tramite questa istruzione
si può fargli utilizzare l'intero pacchetto:
context.Imports.AddType(typeof(Math));
Questa si può utilizzare con qualunque namespace o classe, di questa impor-
terà tutte le funzioni publiche e statiche.
Nonostante tutte queste funzionalità di Flee non è stato possibile implemen-
tare degli operatori custom come fatto in muParser. Non è stato altresì
possibile permettere la gestione dei Constraint.
All'utente verrà data la possibilità di scegliere tra i due Constraint Store a
seconda delle sue esigienze: in caso di performance si consiglia Flee, in caso
46
58. di semplicità di scrittura tramite Constraint si consiglia muParser.
Per una descrizione più approfondita di Flee, si rimanda al sito del progetto.
4.2.2 Denition Store
Come è stato denito il Constraint Store per la gestione dei vincoli, ora verrà
analizzato il lavoro che è stato svolto per il Denition Store. Dalla teoria e
dalle fasi di analisi e progettazione è emerso che il Denition Store deve:
• Gestire la creazione di una denizione
• Gestire la modica delle denizioni
• Fornire un'interfaccia al ParserAM per aiutarlo nella creazione delle
denizioni
• Istanziare su richiesta le denizioni
Oltre a fornire le funzioni sopra esposte, il Denition Store deve creare degli
oggetti utilizzabili dal Runtime Manager, ossia Agenti pronti ad eseguire le
proprio azioni.
Si è subito deciso che il Denition Store dovesse, in primis, contenere una
lista di denizioni. Il passo successivo è stato decidere che, al momento della
creazione di una nuova denizione, questa venisse fornita al chiamante.
In questo modo il chiamante, utilizzando i metodi della denizione può mo-
dicarla aggiungendoci azioni e variabili locali.
Questo è possibile grazie all'implementazione di interfacce contenute nel Pro-
getto Commons, in comune all'intera soluzione.
Il Denition Store contiene tutte le classi che deniscono le azioni viste in
teoria. Queste sono intercambiabili in quanto tutte implementano la classe
IAction (presente anch'essa in Commons).
Avremo quindi le seguenti azioni:
• Guard: gestisce un'azione di tipo guardia
• Choose: gestisce una scelta
• Parallel: gestisce un parallelo
• Call: gestisce la chiamata
• Tell: gestisce un Tell a rate innito (all'interno dei Parallel)
• Ask: gestisce un Ask a rate innito (all'interno dei Parallel)
Oltre alle azioni e le denizioni, il Denition Manager deve fornire anche gli
Agenti.
47
59. Le Azioni
Le azioni (come visto nel capitolo 2.2.6) vengono raccolte in ActionManager
che a loro volta vengono contenuti da Actions. Actions gestisce più Action-
Manager (come fosse una scelta), mentre un ActionManager gestisce una lista
di Azioni nchè si trova il bivio dato da una scelta.
È ora riportato un esempio che aiuterà a spiegare l'utilizzo di Actions e
ActionManager.
Ag :- [- X=X+1]@{1} . Ag . [X 0 - ]@{X}.Ag + [ - ]@{1}.Ag2
+ [ X5 - X=0]@{1} . Ag || Tell(Y=X);
Questo agente avrà come azioni un oggetto costituito come quello in gura:
Le azioni implementano l'interfaccia IAction, che garantisce la loro esposi-
zione dei seguenti metodi:
• Execute: fa eseguire l'azione
• ReceiveLocalVariables: permette di impostare le variabili locali all'a-
zione
• GetRate: ritorna il rate dell'azione
• GetAskVars: ritorna le variabili che inuiscono sull'eseguibilità dell'a-
zione
• GetTellVars: ritorna le variabili che vengono modicate dall'azione
• Refresh: aggiorna l'azione se sono state modicate variabili che la
interessano
• IsRateInnite: permette di sapere se l'azione ha rate innito
48
60. • ForceRefresh: forza il refresh
• GetActionData: ritorna un oggetto con i dettagli dell'azione
L'utilizzo di funzioni come Refresh, GetAskVas e GetTellVars sono indispen-
sabili al ne di ottenere un miglioramento delle performance. Nella sezione
dedicata al Constraint Store, vedremo come queste verranno utilizzate.
Agent e AgentDenition
AgentDenition è una classe che contiene tutte i dati utili per la creazione
di un agente e i metodi per far si che il parser possa crearla.
Agent viene creato dalla AgentDenition ed è l'oggetto centrale di sCCP.
Dalla teoria si evince che un agente è costituito dalle Variabili Locali e dal-
l'elenco delle proprie azioni. Questo è importante anche perchè ci fa capire
che inizialmente tutti gli agenti dello stesso tipo sono uguali.
Da questo si può desumere che l'istanziazione di un Agent da un AgentDe-
nition sia alquanto semplice in quanto consta della crezione dello stesso e del
passaggio delle proprie azioni e delle proprie variabili locali. Nello specico
ogni Agent implementerà l'interfaccia IAgent. Per le speciche dell'interfac-
cia IAgent e delle altre, si rimanda ai sorgenti del codice, dove i commenti
spiegano l'utilizzo di ogni funzione.
Verrà ora spiegato velocemente cosa succede quando viene invocato l'Excecu-
te a un Agente. Assieme all'Execute viene passato un valore è Reale: questo
generato dal Runtime Manager e serve nella scelta dell'azione.
Vediamo ora uno pseudo codice:
Execute(RATE)
i := 0
while(RATE 0)
RATE = RATE - RateAzione[i]
if (RATE 0)
Esegui Azione[i]
else
i ++
Nello specico tale azione viene compiuta dall'oggetto Actions che ogni Agent
ha. Azione[i] è un ActionManager e come tale inizierà l'esecuzione eseguen-
do ogni IAction che ha in lista. Se dovessimo incontrare un'azione di tipo
Choose, questa gestirà autonomamente (con il rate residuo) la scelta che deve
fare.
49
61. 4.2.3 Runtime Manager
Il Runtime Manager è forse l'elemento più importante dell'intero modulo: è
il vero e proprio motore della simulazione. Nella fase di progettazione si sono
discusse la operazioni che deve sostenere, in questa fase vedremo, in forma
ridotta, gli elementi principali.
Un possibile pseudocodice per rappresentare l'operato di uno Step simulativo
è il seguente:
if (IsWorkEnded è vero) return;
if (Ci sono Agenti Con Rate Infinito)
Esegui il Primo
Rimuovilo dalla lista
else
Ottieni il Rate Totale
Execute(Rate Totale * Rand(0,1))
Aggiorna il tempo
if (Il tempo passato supera la precisione)
Aggiorna GraphData
Terminazione
La condizione di IsWorkEnded viene posta vera dopo il raggiungimento del
criterio di arresto. Questo può essere di due tipi: temporale o sul numero di
passi.
Questo ag è accessibile anche dal chiamante in modo che sappia quando il
lavoro è terminato.
Agent Pool
Agent Pool è una classe appoggio del Runtime Manager che gestisce gli agen-
ti. Nello specico ritorna i valori del Rate del Sistema, sceglie l'agente da
eseguire in base al valore casuale generato dal Runtime manager e gestisce
l'eliminazione degli agenti dopo l'esecuzione.
Una delle cose più importanti che vengono fatte dall'Agent Pool è la gestione
dei Refresh. Questi sono gestiti in maniera intelligente ragioni di performan-
ce.
Prima si è mostrato lo scheletro della classe IAction e si è rimandato il trat-
tamento di metodi come GetAskVars e GetTellVars. Questi servono per
l'appunto per la gestione dei Refresh.
Dopo ogni esecuzione, Agent Pool controlla quali sono le variabili modicate
50
62. dell'azione che ha eseguito (GetTellVars) e sa quali sono gli agenti (memoriz-
zati in opportune liste) che subiscono un cambiamento a causa delle variabili
globali cambiate. Questo può essere di due tipologie:
• Modica di variabili presenti nell'Ask
• Modica di variabili presenti nel Rate
Se nessuna delle due condizioni si verica, allora quel dato agente avrà lo
stesso stato del passo precedente (valori delle guardie e rate). In questo mo-
do si migliora l'ecienza mano a mano che sale il numero di agenti. È facile
immaginare sistemi con centinaia di agenti di tipi diversi, che condividono
poche variabili: in questi casi l'aumento di ecienza è molto rilevante.
Se durante il Refresh un'azione ha rate innito, questa avvisa subito il Run-
time Manager che mette l'agente nella coda speciale dei Rate inniti. L'ope-
razione di aggiornamento continua e se vi sono altre azioni a Rate innito,
queste vengono messe in coda. In questo modo al passo successivo, eseguirà.
Aggiornamento GraphData
GraphData è stato costruito per fornire lo stato delle variabili. Inizialmente
era costituito da un dizionario di stringhe e liste di double in cui ogni lista
tracciava l'andamento della variabile indicata dalla stringa.
Ad esempio con:
GraphData[X ]
Si ottiene la lista di valori che ha assunto la variabile X. Per motivi di perfor-
mance alla classe GraphData è stato aggiunto l'elemento Time. Questo viene
modicato nel momento in cui vengono inseriti i nuovi valori nel dizionario.
È stato implementato in questo modo per favorire il lettore dei dati: basta
vedere se è stato incrementato Times piuttosto che contare tutti i valori nelle
liste.
4.2.4 ParserCS
Il ParserCS è una classe costruita per leggere e interpretare degli opportuni
les XML. Questi devono fornire una modellazione del Constraint Store e
come tale possono contenere:
• Dichiarazioni di Variabili
• Dichiarazioni di Parametri
51