giochi su grafi visibly pushdown rispetto a strategie modulari

Transcript

giochi su grafi visibly pushdown rispetto a strategie modulari
UNIVERSITA' DI SALERNO
Facoltà di Scienze Matematiche Fisiche e Naturali
CORSO DI LAUREA IN INFORMATICA SPECIALISTICA
TESI DI LAUREA
GIOCHI SU GRAFI VISIBLY PUSHDOWN RISPETTO A
STRATEGIE MODULARI
CANDIDATO
Ilaria De Crescenzo
matr.0521000308
RELATORE
Chiar.mo Prof.
Salvatore La Torre
ANNO ACCADEMICO 2010/11
Indice generale
Introduzione................................................................................................................... 4
Capitolo 1..................................................................................................................... 12
Gli Ω-Automi...............................................................................................................12
1.1 Introduzione .......................................................................................................12
1.2 Gli ω-automi su parole........................................................................................13
1.3 Gli ω-automi su alberi.........................................................................................15
1.4 Gli automi universali, esistenziali e alternanti.....................................................17
1.5 Condizioni di accettazione..................................................................................19
1.6 Differenza fra automi di Büchi non-deterministici e deterministici....................20
1.7 Proprietà di chiusura degli ω-linguaggi regolari..................................................23
1.8 Il problema del vuoto..........................................................................................25
Capitolo 2..................................................................................................................... 27
Le Logiche temporali .................................................................................................27
2.1 Dalle logiche classiche alle logiche temporali.....................................................27
2.2 Il modello ...........................................................................................................28
2.2.1 Le strutture di Kripke...............................................................................................29
2.3 Operatori temporali ............................................................................................31
2.4 La superclasse CTL*...........................................................................................35
2.4.1 Sintassi.....................................................................................................................35
2.4.2 Semantica.................................................................................................................36
2.5 CTL e LTL..........................................................................................................37
2.6 Model checking LTL...........................................................................................38
Capitolo 3..................................................................................................................... 43
Una logica per rappresentare chiamate ricorsive: CaRet.......................................43
3.1 Perchè introdurre CaRet......................................................................................43
3.2 Le computazioni strutturate.................................................................................44
3.3 Il modello: RSM..................................................................................................47
3.4 Sintassi e semantica di CaRet.............................................................................50
3.5 Esempi di proprietà verificabili con CaRet........................................................52
3.6 Verifica formale delle formule della logica CaRet..............................................55
1
3.7 Passare da RSM al RGBA...................................................................................57
Capitolo 4 ....................................................................................................................61
Giochi su grafi.............................................................................................................61
4.1 Il contesto rappresentato dai giochi su grafi........................................................61
4.2 Le caratteristiche di un gioco..............................................................................62
4.3 Definizione formale di giochi su grafi finiti........................................................63
4.3.1 Definizione di grafo di gioco...................................................................................63
4.3.2 Definizione di partita o run......................................................................................64
4.3.4 Definizione di gioco e di insieme vincente..............................................................64
4.4 I giochi su RSM..................................................................................................66
4.4.1 Motivazioni..............................................................................................................66
4.4.2 Giochi su grafi ricorsivi...........................................................................................66
4.4.3 Stato del gioco..........................................................................................................70
4.4.4 Run di un grafo di gioco ricorsivo...........................................................................70
4.4.5 Funzione CTR..........................................................................................................71
4.4.6 Funzioni s-history, storia (Hts) e storia locale (μ)....................................................72
4.4.7 Condizioni di vincita................................................................................................73
Capitolo 5..................................................................................................................... 74
Le strategie modulari .................................................................................................74
5.1 Motivazioni.........................................................................................................74
5.2 Definizione formale di strategia modulare..........................................................74
5.3 Risolvere giochi con strategie modulari..............................................................75
5.3.1 Gli automi alternanti................................................................................................76
5.3.2 I Δ-labelled k-tree.....................................................................................................78
5.3.3 I two-way alternating parity tree automaton............................................................78
5.3.4 Alberi di strategia.....................................................................................................80
5.3.5 Automi che accettano alberi di strategia vincenti con condizioni di safety.............83
5.3.6 Passare da condizioni di safety ad altre specifiche ω-regolari.................................84
5.3.7 Complessità..............................................................................................................86
Capitolo 6..................................................................................................................... 87
Risolvere giochi ricorsivi con specifiche CaRet usando strategie modulari............87
6.1 Introduzione........................................................................................................87
2
6.2 Il processo di trasformazione da (G,φ) a (G',W').................................................88
6.2.1 Costruzione Formale................................................................................................91
6.2.2 Dati in input..............................................................................................................92
6.2.3 Costruzione del gioco (G',W')..................................................................................93
6.3 Da condizioni di Büchi-coBüchi a condizioni 1-Rabin.......................................98
6.3.1 Costruzione formale di (G'',W'')...............................................................................99
6.4 Da condizioni 1-Rabin a condizioni di parità....................................................101
6.5 Costruire l'albero di strategia Astr.....................................................................102
6.6 Costruzione dell'automa Awin con condizioni di parità e risoluzione del problema di
decidibilità.......................................................................................................................104
Conclusioni.................................................................................................................106
Bibliografia................................................................................................................108
3
Introduzione
Lo studio degli automi a stati finiti ricorsivi (o RSM) nasce dall'esigenza di
formalizzare e analizzare i comportamenti dei sistemi a chiamate ricorsive. Essi sono
costituiti da componenti logiche o fisiche distinte, detti moduli, che possono generare
invocazioni alle altre componenti per mezzo di chiamate a procedura. Una componente,
durante la sua computazione, può passare il controllo ad un altro modulo, che si
occuperà di proseguire il processo a partire dal proprio punto di entrata. Se il flusso
dell'esecuzione giunge ad uno dei punti di uscita del modulo chiamato, il controllo
viene spostato da esso al modulo chiamante, che sarà responsabile di proseguire la
computazione dal punto in cui si era fermato. Scenari simili sono ampiamente
riscontrabili nel panorama informatico e un esempio classico di strutture di questo tipo
sono i flussi di controllo generati dai linguaggi di programmazione di tipo imperativo o
dalle architetture distribuite.
In modo più formale un RSM viene rappresentato da un insieme di moduli,
ognuno dei quali è costituito da un insieme di nodi, o stati atomici, e box, che hanno lo
scopo di mappare i moduli esterni. Ogni modulo possiede una “interfaccia”, costituita
da i suoi nodi di entrata e i suoi nodi di uscita. Gli archi, che corrispondono alle
transizioni del sistema, possono connettere nodi a nodi o nodi a box. Un arco che da un
nodo entra in una box rappresenta una invocazione del modulo attuale al modulo che
tale box rappresenta e questa operazione prende il nome di call. Un arco che connette
un punto di uscita di una box a un nodo del modulo indica lo stato in cui ritorniamo
dopo aver invocato una determinata componente: tale situazione verrà chiamata return.
Nel campo della verifica formale lo studio degli RSM e degli automi pushdown ha
trovato ampio interesse [7,45], sia per il considerevole numero di scenari
rappresentabili tramite essi che per la varietà di tematiche che vengono coinvolte
durante loro trattazione. Infatti, oltre a comprendere analisi e ottimizzazione dei
costrutti, per effettuare verifica di macchine ricorsive ci si trova a dover considerare
proprietà nuove e peculiari derivanti della struttura atipica del modello. La maggior
4
parte delle novità introdotte sono in particolare legate all'occorrenza delle call e dei
return e ai legami che le varie invocazioni creano fra le componenti. Le richieste di
determinate proprietà fra questi eventi, come verifica di pre-post condizioni e controllo
dello stack, per effetto dell'annidamento delle chiamante generano linguaggi non
regolari context-free, il cui studio è strettamente connesso alla correttezza di linguaggi
di programmazione object oriented [12].
Anche l'applicazione delle tematiche attinenti alla teoria dei giochi su grafi su
RSM (chiamati recursive game graph o RGG) ha aggiunto prospettive alle tecniche
classiche e introdotto approcci nuovi tipici esclusivamente di questi modelli. Un gioco è
costituito principalmente da un'arena, ossia un grafo, e da delle condizioni di vincita.
L'arena nel nostro caso non è altro che un RSM in cui l'insieme dei vertici è
partizionato fra due o più giocatori, di cui almeno uno deve essere il giocatore
esistenziale. In questo lavoro prenderemo in considerazione solo i giochi su grafi a due
giocatori. Per condizioni di vincita possono essere considerate tutte le specifiche che
possono essere richieste su un normale automa. Data una partita, ossia una stringa
infinita di stati tali che fra uno stato e il successivo vi sia sempre una transizione lecita
nel grafo di partenza, la partita viene definita vincente per il giocatore esistenziale se il
run rispetta le condizioni di vincita imposte per il gioco.
L'originale motivazione dietro lo studio dei giochi in ambito di verifica formale
risiede nell'importanza di risolvere quello che in letteratura viene definito il problema
della sintesi del controllore: dato un modello di un sistema in cui parte scelte non sono
controllabili, si vuole realizzare un meccanismo che nei punti controllabili fornisca un
input al sistema in maniera tale da preservarne la correttezza. Il calcolo della
complessità per giochi pushdown è stato studiato[13] e sono stati costruiti algoritmi per
risolvere giochi su di essi assumendo che i partecipanti abbiano una storia completa
delle mosse effettuate fino a quel momento durante tutta la partita[5], un approccio
classico che prende il nome di strategia globale.
La separazione delle componenti negli RGG però ha portato a considerare anche
un altro tipo di scenario: è possibile risolvere giochi su modelli ricorsivi nel caso in cui
il controllore possegga una conoscenza parziale della storia delle mosse? La struttura
modulare permette di contemplare situazioni nella quali uno solo o entrambi i giocatori
5
conoscano solo quello che è accaduto e sta accadendo all'interno del modulo corrente.
Costruire strategie che affrontino questa problematica nei casi in cui la conoscenza
limitata sia del giocatore esistenziale consentirebbe la realizzazione di componenti la
cui correttezza è garantita indipendentemente dal contesto in cui vengono invocate. La
progettazione di un meccanismo di controllo autonomo che concettualmente meglio si
adatta alla struttura distruibuita del modello renderebbe il sistema meglio gestibile nella
pratica.
Valutare questa situazione, assolutamente impossibile da riscontrare in altre
tipologie di sistema,
ha portato all'introduzione di un nuovo concetto formale di
strategia, detta modulare[4], in cui le decisioni del giocatore esistenziale vengono prese
solo sulla base degli eventi accorsi nel modulo corrente. Questa tematica viene trattata
nel dettaglio in [3], studio nel quale viene progettato e dimostrato un approccio teorico
basato sugli automi. L'articolo prende in esame determinate casistiche in cui le
condizioni di vincita sono esterne ed espresse come linguaggi ω-regolari: automi di
Büchi/Co-Büchi deterministici, automi di Büchi/Co-Büchi universali e formule in
logica LTL. Date le specifiche esterne, come condizione viene richiesto che la strategia
usata in ogni momento all'interno di un modulo sia la stessa ogni volta che lo stesso
modulo viene invocato e che la strategia vincente debba riconoscere cicli accettanti.
L'algoritmo proposto prende in considerazione il prodotto cartesiano fra il grafo di
gioco, rappresentato come un RGG, e l'automa costituente le condizioni di vincita. Per
trovare una strategia vincente che sia modulare su tale prodotto viene costruito all'inizio
un albero di strategia. Questo costrutto è formato da un nodo radice e, ad esso collegati,
da una serie di sottoalberi, uno per ogni modulo. Essi hanno lo scopo di codificare le
scelte che devono essere effettuate ogni volta che si entra nel modulo a cui si
riferiscono. Questa tecnica permette di vincolare la strategia a mantenere lo stesso
approccio ogni volta che la componente viene invocata. Successivamente viene definito
un automa two-way alternating tree, con l'obiettivo di simulare le transizioni
individuate nell'albero di strategia e di assicurarsi che il gioco inizi nel corretto punto di
entrata al modulo iniziale e che da esso non esca. Inoltre esso deve controllare la
consistenza dell'albero in input rispetto alle uscite che devono essere evitate e che erano
state individuate già nell'albero di strategia originario. Nel caso di automi deterministici
6
di Büchi, il problema di decidere se esista una strategia modulare che garantisca che un
automa A sia accettato è EXPTIME-complete, traducendo in modo esponenziale
l'albero alternante in un automa non-deterministico [42] e controllando il vuoto di
Büchi in tempo polinomiale. Per RGG i cui nodi sono etichettati con proposizioni
atomiche, data una formula in LTL, il problema di decidere se esista una strategia
modulare tale che i cammini soddisfino la formula risulta essere 2EXPTIME-complete.
LTL è una logica nata per esprimere proprietà di correttezza per sistemi reattivi e
la sua applicazione su gli RSM è stata ampiamente trattata[1,11,25] e implementata in
diversi tool e programmi di analisi per model checking di programmi in C o Java
[16,37]. In [21] viene anche proposta una tecnica che usa LTL nei sistemi pushdown e
che permette alle proposizioni atomiche di corrispondere a un linguaggio regolare
valutato sullo stack delle chiamate. Tuttavia le specifiche espresse tramite LTL
risentono delle limitazioni dovute ai tipi di operatori e ai costrutti in essa definiti,
essendo stati progettati per esprimere requisiti sequenziali. Il flusso di controllo tipico
dei sistemi ricorsivi è caratterizzato dal ricorrere di call e return, alcuni fra essi
necessariamente associati e corrispondenti, e dunque potrebbe essere interessante
nonché necessario controllare proprietà assolutamente non formalizzabili per mezzo dei
soli costrutti offerti da LTL.
Come nuovo strumento di specifica di proprietà per automi ricorsivi, abbiamo
scelto di prendere in considerazione una logica temporale, CARET, che genera proprietà
associabili a linguaggi non regolari, ma decidibili[2]. CARET eredita tutti i formalismi di
LTL, ma arricchisce l'espressività delle formule introducendo una serie di nuovi
costrutti progettati ad hoc per le macchine ricorsive. Questo permette di formalizzare
con facilità un insieme di nuove specifiche, concentrate a verificare la correttezza
parziale o totale nel rispetto delle pre e post condizioni e supportare il controllo dello
stack. La principale innovazione consta nell'offrire una reinterpretazione di alcuni tipici
operatori di LTL(Until, Next, Release, etc...), fornendo per ognuno diverse “modalità”.
L'uso delle varie modalità permette di riferirsi esplicitamente ai successori su cui
l'operatore andrà a incidere e questo consente di cambiare a seconda delle esigenze la
visibilità degli stati considerati e di verificare condizioni
all'interno dello stesso
modulo, oppure fra modulo e modulo, o ancora fra modulo e modulo chiamate. Le tre
7
modalità che vengono previste sono: successore “globale”, per mezzo del quale
esplicitiamo che l'operatore deve essere valutato sui successori del nodo attuale, siano
essi appartenenti allo stesso modulo o ad un altro che può essere invocato; successore
“astratto” (o locale), per mezzo del quale indichiamo che l'operatore deve essere
valutato su i successori “locali”, ossia solo su quei nodi successivi al nodo attuale ma
che appartengono allo stesso modulo (quindi se al punto attuale eseguiamo una call, la
proprietà deve essere richiesta sulla posizione del successore astratto, ossia lo stato
raggiunto dopo l'uscita dal modulo invocato); la modalità “caller”, in cui esplicitiamo
che l'operatore deve essere valutato sulla più recente chiamata non associata. Le
differenziazioni di vista consentono di poter formalizzare condizioni del tipo “se viene
invocato il modulo B allora deve essere invocato solo dal modulo A” oppure “Se è vera
la condizione p al momendo di invocare un modulo, quando ritorno da esso deve essere
vera q”, tutte caratteristiche non specificabili per mezzo delle logiche temporali
tradizionali. Il risultato più importante ottenuto in [2] è che viene dimostrato che, data
una formula φ espressa in logica CARET e un RSM S etichettato con proposizione
atomiche, se modello e specifiche vengono sincronizzati sui simboli call e ret, allora
controllare se S soddisfa φ diventa un problema decidibile. La dimostrazione si fonda
su una generalizzazione del metodo di costruzione basato sui tableau per LTL,
trasformando il modello in un nuovo automa ricorsivo S ¬φ con condizioni generalizzate
di Büchi tale che S¬φ ha un run accettante se e solo se S aveva una computazione che
violava φ. Tale costruzione richiede complessità di tempo polinomiale nella taglia di S e
esponenziale nella taglia di φ.
L'esistenza di una logica costruita con lo scopo specifico di esprimere proprietà su
sistemi a chiamate ricorsive ci ha spinto ad interrogarci riguardo una questione
specifica: se avessimo un grafo di gioco ricorsivo G etichettato con formule atomiche e
una specifica φ in logica CARET, è possibile decidere se esista o meno una strategia
modulare tale che qualsiasi run su G generato da quella strategia soddisfi φ? Il primo
obiettivo che abbiamo cercato di raggiungere era trovare un sistema per costruire un
nuovo gioco che accetti tutti quei run in G che soddisfano φ. In un approccio simile a
quello impiegato per dimostrare se un normale RSM soddisfa una formula CARET, i
nostri sforzi si sono concentrati sul integrare le condizioni imposte dalla formula
8
all'interno della modello del gioco, eliminando tutte quelle porzioni di grafo non
necessarie e controllando che il nuovo grafo ottenuto rispetti le transizioni originarie, se
l'etichettamento imposto sui nodi è ovviamente in accordo con regole determinate dagli
operatori e dalla struttura della formula. Per rendere più facilmente gestibile e
comprensibile la procedura di decisione sull'esistenza di strategie modulari per il nuovo
gioco risultante, abbiamo raffinato la costruzione attraverso dei passaggii intermedi,
fino ad ottenere un grafo ricorsivo in cui le condizioni fossero espresse come condizioni
di parità. Questo nuovo gioco (Gφ, colour), che aveva strategie vincenti per il
giocatore0 se e solo se vi erano strategie vincenti per quello stesso giocatore nel il gioco
G con specifica φ e viceversa, può essere dunque trasformato agevolmente in un
automa two-way alternante Awin con condizioni di parità. Come più volte sarà
evidenziato durante questo lavoro, quest'ultimo passaggio si discosta solo leggermente
da quello proposto per giochi con condizioni esterne. Convertendo infine Awin in un
automa one way non-deterministico e considerando la sua intersezione con un albero di
strategia, potremmo infine ottenere un automa A' sempre one-way non-deterministico
che accetti un albero se e solo se questo albero corrisponde ad un albero di strategia
vincente per il giocatore0, risolvendo dunque il problema della decidibilità.
Alla luce dell'overview fornita fino a questo momento, il lavoro di tesi svolto si
presenterà con la seguente struttura:
Nel Capitolo 1 verranno presentate alcuni concetti basilari della teoria degli ωautomi, nozioni e strumenti che sono stati ritenuti necessari da esplicitare poiché
ritenuti fondamentali per la comprensione dei capitoli successivi e che verranno
impiegati nel corso del testo. Verranno esplicitate le definizioni formali per le varie
classi di ω-automi e la distinzione fra quelle che sono le principali condizioni di
accettazione per gli automi su parole infinite. Una particolare attenzione è stata dedicata
alla presentazione e all'analisi degli automi di Büchi, delle problematiche derivanti
dall'asimmetria fra i deterministici e i non-deterministici, delle proprietà di chiusura dei
linguaggi da essi riconosciuti e dell'importanza del risolvere il cosidetto vuoto di
Büchi. Tale argomento nel capitolo 2 verrà esaminato con maggiore attenzione e
associato al contesto della logica LTL.
Nel Capitolo 2 verrà dato spazio alla descrizione delle logiche temporali,
9
presentando il modello su cui vengono applicate (le strutture di Kripke) e il significato
degli operatori tipici di tali logiche e, infine, fornendo le definizioni formali per
alfabeto, sintassi e semantica delle più comuni logiche temporali, CTL*, CTL e LTL.
Un paragrafo a parte è stato dedicato alla trattazione del rapporto fra automi di Büchi e
LTL, come ridurre il problema del model checking LTL al problema del vuoto di Büchi
e come risolverlo. Questi ultimo argomento risulta essere indispensabile per la
comprensione dei capitoli successivi, poiché numerose delle dimostrazioni presenti nei
capitoli 3 e 4 basano il loro funzionamento proprio sulla equivalenza dei linguaggi
espressi da formule LTL e dagli automi di Büchi.
Nel Capitolo 3 viene incentrata l'attenzione sulla presentazione della logica
CARET, soffermandosi sui modelli su cui essa opera e sulle caratteristiche nuove che in
essa vengono introdotte. Un riguardo particolare è stato dedicato alla definizione dei
nuovi tipi di successori astratto, locale e caller, e all'importanza del loro uso
nell'espressività delle specifiche. Per completare il quadro delle definizioni, verranno
anche forniti alcuni esempi delle specifiche effettuabili tramite l'uso dei suoi costrutti e
del loro significato. L'ultima parte del capitolo è stata interamente incentrata su come
viene dimostrata la decidibilità del problema del model checking per specifiche
CARET e l'integrazione all'interno dell'RSM di partenza delle condizioni che una
formula in input impone sul modello di partenza. Da questa costruzione verrà tratto
spunto per rendere le condizioni di CARET interne agli RGG nel Capitolo 6 e ha
costituito un risultato fondamentale per lo sviluppo successivo di questa tesi.
Nel Capitolo 4 viene fatta una introduzione per esplicitare il concetto di gioco su
grafo, per mezzo delle definizioni formali delle componenti che lo contraddistinguono,
come il concetto di gioco (o arena), di partita, di insieme vincente e di strategia.
Successivamente si sono calate le definizioni nel contesto delle macchine ricorsive,
analizzando attentamente i cambiamenti che i costrutti assumono. Un riferimento
particolare viene fatto anche anche alle funzioni relative all'estrapolazione della storia
dai vari run. Le strutture che sono esplicitate in questo capitolo saranno largamente
utilizzate nei capitoli successivi e verrà presa per certa l'esatta comprensione delle loro
componenti e dei loro comportamenti.
Il Capitolo 5 è interamente dedicato alle strategie modulari, alla loro definizione e
10
alle modifiche che l'approccio di tipo modulare comporta. Largo spazio è stato dedicato
all'algoritmo basato sulla costruzione di automi che risolve il problema della decibilità
dell'esistenza di strategie modulari per condizioni di tipo safety, esteso successivamente
per la risoluzione di specifiche con automi di Büchi/Co- Büchi e LTL. Da questo
contesto, i paragrafi relativi alla costruzione degli alberi di strategia e degli automi
alternanti che li riconoscono sono gli argomenti da cui abbiamo tratto maggiori
informazioni per risolvere il problema della decibilità nel caso di specifiche CARET. La
quasi totalità dei loro meccanismi è stata applicata senza la necessità di particolari
modifiche nella parte finale del processo di costruzione presentato nel Capitolo 6.
Il Capitolo 6 presenta nel dettaglio la soluzione proposta per risolvere il problema
di decisione riguardo l'esistenza di una strategia modulare vincente per giochi ricorsivi
con specifiche CARET espresse da una formula φ. Prima di ogni costruzione formale, si
è scelto di analizzare passo passo i tipi di problematiche riscontrate e il processo logico
che ha portato ad effettuare le specifiche scelte progettuali. L'introduzione di tecniche
per gestire i nuovi vincoli ha portato allo sviluppo di una procedura che si discosta nelle
sue fasi iniziali sensibilmente dalle tecniche presentate per le altre classi di specifiche e
abbiamo ritenuto opportuno adottare questo approccio per una più comprensibile e
accurata trattazione. Le fasi che vedremo saranno: il passaggio dal gioco (G, φ) di
partenza con una specifica CARET
φ a un gioco (G', W') con W condizione di
Buchi/Co-Buchi; l'introduzione di un parametro j che permetterà di definire un nuovo
gioco ricorsivo (G'',W'') in maniera tale da rappresentare le stesse condizioni come una
coppia di insiemi alla Rabin; infine, la trasformazione di queste ultime condizioni in
condizioni di parità, definendo una colorazione che abbia riconosca gli stessi run e
ottenendo un nuovo gioco (G'',Colour). Viste nel dettaglio tutte le strutture e i passaggi
precedenti, risulterà immediata la generazione dell'automa one-way non-deterministico
che riconosce alberi di strategia vincenti.
Nelle Conclusioni abbiamo riservato spazio a una breve carrellata delle tematiche
trattate e dei risultati ottenuti, dei limiti della soluzione proposta e dei possibili sviluppi
futuri in cui la ricerca può indirizzarsi trattando questo settore.
11
Capitolo 1
Gli Ω-Automi
1.1 Introduzione
Da quando gli automi su parola infinite, o ω-automi, e gli ω-linguaggi da essi
generati hanno fatto il loro ingresso nella teoria degli automi con la loro applicazione
nelle procedure di decisione per le teorie di secondo ordine della logica classica [10], i
loro costrutti e le loro proprietà hanno sempre attirato l'interesse della comunità
scientifica per la loro capacità di essere applicati nella specifica e nella verifica dei
sistemi reattivi il cui comportamento possiede la peculiarità di essere teoricamente
predisposto a non terminare. Tali tipologie di sistemi possono essere trovate
abbondantemente nell'ampio panorama della programmazione e ne sono esempi tipici i
sistemi operativi, i protocolli di rete, i programmi di controllo, le computazioni che
coinvolgono agenti multipli e così via.. Inoltre lo studio dei modelli che operano su
parole infinite risulta essere valido anche per l'analisi dei casi di input finiti, poiché per
ottenere il caso finito basta inserire un loop su se stesso per lo stato che si vuole rendere
finale e l'automa così costruito opera correttamente su stringhe di input infinite.
Il principale scopo di questo capitolo, oltre che formalizzare la definizione dei
vari ω-automi e delle loro proprietà, è anche esplicitare come vengono accettate le
parole infinite. La nozione di accettazione per automi che operano su input finiti è un
concetto abbastanza semplice e ben conosciuto: vengono definiti degli stati finali e se
un input ci permette di terminare la computazione in uno di essi, allora la parola presa
come input è appartenente al linguaggio dell'automa. Questa procedura sarebbe
completamente inapplicabile per modelli che gestiscono sequenze infinite di simboli e,
dunque, diventa necessario definire condizioni di accettazione più complesse e, come
vedremo in seguito, vi sono diverse possibili scelte (per un'overview del problema
consultare [41]).
12
1.2 Gli ω-automi su parole
Sia Σ un alfabeto finito di simboli. Una parola infinita, o ω-word, su Σ è una
sequenza infinita di simboli di Σ giustapposti. Denotiamo con Σ
ω
l'insieme delle ω-
parole su Σ. Un ω-linguaggio L è un insieme di ω-word tale che L⊆  .
Formalmente un ω-automa deterministico è una quintupla
A=(Σ,Q,q0,δ,F)
dove:
•
Σ è un insieme di simboli, detto alfabeto
•
Q è l'insieme finito degli stati
q 0 ∈Q è lo stato iniziale
•
•
δ : Q × Σ → Q è la funzione di transizione
•
F sono le condizioni di accettazione, formalmente F ⊆Q
Un run di A è una sequenza ρ = (s0, s1, s2, …, sn, …) tale che s0=q0 ed qi → qi+1
∀ i∈ℕ
Un ω-automa non-deterministico è una quintupla
A=(Σ,Q,Q0,Δ,F)
dove:
•
Σ è un insieme di simboli, detto alfabeto
•
Q è l'insieme finito degli stati
•
•
Q0 ⊆Q è l'insieme degli stati iniziali
δ : Q × Σ → pow(Q) è la funzione di transizione di transizione dove pow(Q) è il
13
power set su Q
•
F sono le condizioni di accettazione, formalmente F ⊆Q
Data una parola w, un run di A su w è una sequenza ρ = (s0, s1, s2, …, sn, …) tale che
s 0∈Q 0 e ∀ i∈ℕ s i1∈ s i , wi 1 
Anche se le due definizioni sembrano quasi identiche, la caratteristica che rende
enormemente diversi questi due automi è il determinismo/non-determinismo. Se la
funzione di transizione degli automi deterministici in corrispondenza di ogni simbolo ci
permette di transire solo verso uno stato, dunque in corrispondenza di una stringa di
dati, il run da esso generato è univoco, negli automi non-deterministici, potendo da uno
stato avere più transizioni in corrispondenza di uno stesso simbolo in input, una parola
può generare un insieme di run. Un piccolo esempio grafico è riportato nella Figura 1.
Nel caso dell'automa deterministico rappresentato nell'immagine, la parola ab* genera
un run univoco in cui viene visitato prima il vertice v1 e poi si passa al vertice v2. Nel
caso dell'automa non-deterministico, la stessa parola potrebbe portarci a visitare v2,
come invece potrebbe anche farci restare in v1. Se già la differenza fra nondeterminismo e determinismo negli automi a stati finiti su parole finite comportava una
distinzione marcata fra i modelli, negli -automi, il cui potere espressivo è già reso più
complesso dalle diversità derivate dalla condizioni di accettazione, la scelta fra
determinismo e non-determinismo rende la trasformazioni da un tipo di automa a un
altro ancor più delicata e in alcuni casi (come vedremo successivamente negli automi di
Buchi) non è ammissibile.
Figura 1: Diversità tra un automa deterministico e uno non-deterministico
14
1.3 Gli ω-automi su alberi
Gli automi visti in precedenza lavorano su stringe infinite di simboli appartenenti
un alfabeto e in sequenza prendono uno dei simboli e, a partire dallo stato corrente,
determinano il successore sulla base della relazione di transizione. Automi su oggetti
infiniti ricoprono però un importante ruolo in tutti quei frangenti in cui vogliamo
analizzare sistemi non terminanti, dato che le specifiche del sistema possono essere
tradotte in automi su cui possiamo possiamo effettuare problemi di decisione in teoria
degli automi. Tuttavia però automi su alberi spesso sono più adatti rispetto a automi su
parole quando abbiamo bisogno di modellare scenari non-deterministici. Inoltre ci sono
strette connessioni fra automi su alberi e teorie della logica, tramite le quali per esempio
è stata mostrata la decibilità delle logiche monadiche di secondo ordine usando automi
su alberi che elaborano alberi binari [36].
Se gli automi devono operare su alberi infiniti sorge la necessità di modificare il
modello di rappresentazione degli automi stessi per poter gestire gli eventi che
deriverebbero dalla struttura del nuovo input. Faremo il nostro esempio su un albero
binario (e come vedremo la descrizione può essere facilmente estesa da alberi a due
successori ad alberi con k successori), ossia un albero che ha due successori, piuttosto
che uno solo come accade invece per le parole infinite. E' naturale definire che per uno
stato in Q e un simbolo in input appartenente a Σ vi sono due successori nella relazione
di transizione, che ora avrà elementi di tipo Q × Σ × Q × Q. Le computazioni vengono
sempre generate a partire dalla radice dell'albero in input e la computazione va avanti in
base al simbolo in input su tutti i path in parallelo. Una transizione del tipo (q,a, q1, q2)
permetterebbe di passare dallo stato q del nodo u in corrispondenza del simbolo “a”
agli stati q1 e q2 dei nodi u1 e u2. Le prossime transizioni generate verranno proseguite
a partire da entrambi gli stati. Questa procedura genera un Q-labeled tree che noi
chiameremo run dell' ω-automa sull'albero in input.
Un run è accettante se le sequenze di stati costruibili sui path dell'albero
soddisfano le condizioni di accettazione, espresse esattamente come le condizioni di
accettazione degli ω-automi su sequenze.
15
In maniera più formale, un albero binario infinito è un insieme Tω = {0, 1} di
tutte le parole su {0, 1}. Gli elementi u∈ Tω sono i nodi di Tω dove ε è la radice e u0, u1
sono rispettivamente il successore sinistro e e il successore destro del nodo u. Se
abbiamo due nodi u e v appartenenti a Tω, v è un successore di u se esiste w∈ Tω, v =
uw. Una ω-word π ∈ {0, 1}ω è un cammino dell'albero Tω. I nodi di un albero possono
anche essere etichettati tramite una funzione di etichettamento t: Tω → Σ che associa ad
ogni nodo un simbolo dell'alfabeto. Ci riferiremo all'insieme di tutti i Σ-labeled tree
binari con la notazione TΣ.
Se invece di un albero binario etichettato, avessimo un albero con k successori la
definizione sarebbe la seguente: sia k un numero naturale e Δ un alfabeto finito.
Definiremo un Σ-labelled k-tree TkΣ come una coppia (Tk, ν) dove Tk = (Z, E) è un
albero, costituito da un insieme Z di vertici con Z = [k*] e un insieme di archi E = {(y,
y.d) | y ∈[k ✶ ]e d ∈[k ] }. t è una funzione di etichettamento t: Z → Σ che assegna ad
ogni vertice di Z una lettera dell'alfabeto Σ. Con la lettera ϵ denoteremo la radice
dell'albero Tk e per ogni
y ∈Z il vertice dell'albero y.d sarà il suo d-esimo figlio.
Non resta che definire formalmente la nozione di automa su alberi infiniti. La
daremo su alberi binari. Un automa su alberi è una quintupla
A = (Σ, Q, q0 ,Δ, F)
dove:
•
Σ è un insieme finito di simboli, detto alfabeto
•
Q è l'insieme finito degli stati
•
q0 è lo stato iniziale
•
Δ è un sottoinsieme di Q × Σ × Q × Q e rappresenta la relazione di transizione
di A
•
F sono le condizioni di accettazione, formalmente F ⊆Q
Un run di A su un albero in input t∈ TΣ è un albero ρ che appartiene a TQ tale
che ρ(ε) = q0 e per ogni w∈ {0, 1}* vale che (ρ(w), t(w) , ρ(w1), ρ(w2)) ∈ Δ. Ogni
16
cammino del tipo π ∈ {0, 1}ω è un cammino accettante se e solo se soddisfa la
condizione di accettazione espressa tramite F. l'automa A accetta l'albero t se c'è un run
di accettazione di A su t. Il linguaggio riconosciuto da A sarà l'insieme di tutte i run
accettati da A. Per ulteriori informazioni consultare [31]
1.4 Gli automi universali, esistenziali e alternanti
Riferendoci agli automi, abbia parlato di una caratteristica fondamentale che in
cui vengono distinti: automi deterministici, per cui la funzione di transizione è
univocamente definita e l'automa ha un solo run su ogni parola in input, e automi nondeterministici, per i quali uno stesso simbolo può innescare transizioni su uno o più stati
e che, dunque, possiedono per ogni parola più di un run. Dato che ogni parola può
generare uno o più run, la classe degli automi non-deterministici a sua volta viene
divisa in altre due sottocategorie:
•
gli automi universali, ossia che accettano una parola di input se e solo se tutti i
run dell'automa sono accettanti,
•
gli automi esistenziali (a volte detti semplicemente non-deterministici) che
accettano una parola di input se e solo se l'automa ha almeno un run accettante
su tale parola.
Espresso in termini più formali, diremo che dato un automa A e una parola w e un
insieme run(w) che rappresenta tutti i possibili run generati su w, diremo che A accetta
la stringa w se:
•
se A è un automa universale, allora w ∈ L(A) ↔ ∀ρ ∈ run(w) e ρ è accettante
•
se A è un automa esistenziale, allora w ∈ L(A) ↔ ∃ρ ∈ run(w) e ρ è accettante
Tuttavia una terza categoria di automi può essere generata a partire dalla
combinazione di automi universali e automi esistenziali, ossia macchine a stati finiti la
cui caratteristica fondamentale è quella di avere sia il branching universale che quello
esistenziale. Tali automi prendono la definizione di automi alternanti e vengono
rappresentati tramite una quintupla
17
A=(Σ, Q, q0, δ, F)
dove:
•
Σ è un insieme di simboli, ossia l'alfabeto
•
Q è l'insieme degli stati
•
q0 è lo stato iniziale
•
δ è la funzione di transizione, definita come δ: Q × Σ → B+(Q)
•
F è l'insieme degli stati finali
Per B+(Q) viene inteso l'insieme di formule booleane costruite con gli elementi
di Q tramite congiunzioni e disgiunzioni (le negazioni non sono permesse). Quindi la
funzione di transizione mappa una coppia di tipo (stato,simbolo) in una formula
booleana positiva costruita su Q.
La condizione di accettazione di un'automa alternante dipende da come viene
definito l'insieme degli stati finali e può essere una delle tante condizioni che sono
esprimibili su automi.
Per ogni S tale che
S⊆Q e sia φ una formula appartenente a B+(Q), diremo
che S soddisfa φ se l'assegnamento di verità che assegna true agli elementi di S soddisfa
φ.
Le transizioni di automi non-deterministici e universali possono essere formulate
in funzione di B+(Q): ad esempio passare una transizione del tipo δ(q,σ)=(q1, q2, q3) di
un automa non-deterministico esistenziale può essere scritta come δ(q,σ)=(q1 ˅ q2 ˅
q3), mentre se fosse universale basterebbe scriverla come δ(q,σ)=(q1 ˄ q2 ˄ q3).
Il fatto che negli automi alternanti posso avere sia congiunzioni che disgiunzioni
li rende estremamente potenti, poiché la scelta dell'insieme di stati in cui l'automa
decide di andare introduce contemporaneamente non-determinismo esistenziale e
quello universale. Se un automa alternante prevedesse la regola δ(q,σ)=(q1 ˄ q2) ˅ ( q3
˄ q4) allora l'automa accetta il suffisso della parola che inizia con σ a partire dallo stato
q se tale suffisso è accettato partendo da q1 e q2 oppure da q3 e q4.
18
Dunque da un automa non-deterministico o universale possiamo sempre
costruire un automa alternante che riconosca lo stesso linguaggio. Non è sempre
possibile invece, trasformare un automa alternante in un automa esistenziale od
universale, ma possiamo sempre trasformare un alternante di Büchi in un nondeterministico di Büchi e trasformare un alternante di co-Büchi in un universale di coBüchi.
1.5 Condizioni di accettazione
Per parlare di run di accettazione è necessario fare una premessa. Il principale
scopo di un ω-automa è definire l'insieme degli input accettati: nel caso di un automa i
cui input siano di lunghezza finita, sappiamo che una parola è accettata se tale stringa
genera un run che termina in uno stato compreso nell'insieme degli stati accettanti. Per
gli automi con run infiniti la specifica è un po' più complessa e noi in teoria dovremmo
guardare al run ρ nella sua interezza e vedere se tale run è presente fra quelli accettanti
di F. L'insieme delle ω-word accettate è chiamato ω-linguaggio riconosciuto
dall'automa ed è denotato con L(A). La definizione di F come sottoinsieme di Qω è
puramente formale e non adatta alla pratica perchè tali insiemi sono infiniti. Dunque gli
studiosi si sono concentrati principalmente sullo studio di condizioni di accettazione
che sono rappresentabili finitamente. La differenza fra i vari tipi di ω-automa risiede in
come noi specifichiamo e interpretiamo il sottoinsieme F. Nel caso di sistemi che
possono avere computazioni infinite, spesso si è interessati a verificare o analizzare se
determinati comportamenti si ripetono infinitamente spesso. Per qualsiasi run ρ, sia
Inf(ρ) l'insieme di stati che occorre infinitamente spesso in ρ. A partire da questa
definizione possiamo esplicitare le condizioni di accettazione.
•
Automa di Büchi: A accetta tutti quei run per i quali Inf(ρ)∩F ≠ Ø, ossia esiste
almeno uno stato accettante che occorre infinitamente spesso in ρ [10]
•
Automa di Büchi generalizzati: Dato F= {F1, F2, … , Fr} e per ogni 1≤ i ≤ r, Fi
⊆ Q, A accetta tutti quei run per i quali Inf(ρ)∩Fi ≠ Ø, ossia esiste almeno uno
19
stato accettante appartenente a uno dei sottoinsiemi contenuti in F che occorre
infinitamente spesso in ρ. (se F contiene un solo sottoinsieme ci ritroviamo di
fronte a una condizione di Büchi semplice)
•
Automa di Rabin: A è un automa che, dato un insieme di coppie insiemi di stati
R = {(U1,L1), (U2,L2), … (Um,Lm)} con
quali
U i , Li ⊆S , accetta tutti quei run per i
∃i tale che Ui∩Inf(ρ)≠ Ø ˄ Li∩Inf(ρ) = Ø, ossia esiste almeno un run
nel quale compare infinite volte uno stato dell'insieme Ui e non compare
infinitamente nessuno stato del corrispettivo insieme Li [34][35]
•
Automa di Street: A è un automa che, dato un insieme di coppie insiemi di stati
R = {(U1,L1), (U2,L2), … (Um,Lm)} con
U i , L i⊆Q , accetta tutti quei run per i
quali ∃i tale che Ui∩Inf(ρ) ≠ Ø → Li∩Inf(ρ) ≠ Ø, ossia esiste almeno un run
nel quale se visito infinite volte uno stato dell'insieme Ui allora visito
infinitamente spesso uno stato del corrispettivo insieme Li [38]
•
Automa di Muller: A è un automa che, dato un insieme
F ⊆2
Q
e F = {F1, F2,
… , Fn}, accetta un run ρ se e solo se ∃i tale che Inf(ρ) = Fi (è equivalente a
dire che Inf ϱ∈F ) [30]
•
Automa di parità: A è un automa il cui insieme di stati è Q ={0,1,2,...,k} per un
numero naturale k e che accetta i run ρ se e solo se il più piccolo numero in
Inf(ρ) è pari
Per maggiori informazioni sulle condizioni di vincita rimandiamo ai testi [18] [22]
1.6 Differenza fra automi di Büchi non-deterministici e deterministici
Dalla teoria della computazione sappiamo che, usando il meccanismo della
costruzione dei sottoinsiemi, per ogni automa non-deterministico su parole finite
possiamo costruire un automa deterministico su parole finite che ne accetti il
linguaggio, quindi è possibile convertire un automa a stati finiti non-deterministico in
uno deterministico per realizzare una macchina più semplice[24].
Come annunciato nel paragrafo precedente, però, quando parliamo però di ω-
20
automi con condizioni di Büchi, tale trasformazione non può essere effettuate perchè c'è
una differenza fra la potenza espressiva degli automi deterministici di Büchi e quelli
non-deterministici: in particolare questi ultimi risultano essere più “forti”. Per
dimostrare tale affermazione procederemo per assurdo sulla base di un esempio
concreto. Consideriamo l'ω-linguaggio L:= Σ*aω ⊆ Σω e sia l'automa di Büchi nondeterministico che accetta L quello espresso tramite il grafo nella Figura 3, con F={q1}.
Figura 3: Un automa di Büchi che riconosce L
Assumiamo adesso che esista un automa di
Büchi deterministico Adet, tale che
riconosca il linguaggio L:= Σ*aω. Adet, visto che deve accettare tutte le parole di L,
dovra accettare sicuramente la parola aω. Dunque, dopo un prefisso finito, Adet visita
infinite volte uno stato in F. Sia la posizione all'indice n1 il punto dopo il quale si visita
infinite volte lo stato finale. Dal momento che Adet riconosce L, allora dovrebbe
accettare anche la parola an1baω. Sappiamo quindi che dopo un prefisso finito dopo
aver visto b, visitiamo infinitamente spesso uno stato in F. Sia n2 la posizione ( che
compare dopo aver visto b ) dopo la quale l'automa inizia a soddisfare la condizione di
accettazione. Continueremo a osservare che in L è presente anche la parola an1ban2baω
e continueremo così fino ad arrivare a dire che Adet accetta la parola an1ban2ban3ban4...
ma quest'ultima parola non è una parola valida del linguaggio poiché contiene infinite
b.
21
Non solo non è possibile passare da automi di Büchi non-deterministici a quelli
deterministico, ma non possiamo nemmeno garantire che il complemento di un automa
di
Büchi deterministico sia ancora deterministico.
Consideriamo l'automa
deterministico che accetta tutte le parole in cui sono presenti infinite occorrenze di b,
presentato graficamente nella Figura 4.
Figura 4: Un automa deterministico di Büchi che
riconosce infinite occorrenze di b
Ovviamente q0 è l'unico stato accettante. Ogni volta che vedo una b vado nello stato
accettante e negli stati accettanti posso entrare solo con transizioni etichettate con il
simbolo b. Dunque il linguaggio riconosciuto è quello ω-parole che hanno occorrenze
infinite di b.
Il complemento del linguaggio preso in analisi è costituito dall'insieme di tutte le
parole infinite tali che vi è un numero finito di occorrenze di b. Se volessimo realizzare
un automa che accetti solo tali parole, dunque il complemento dell'automa della Figura
4, avremmo un automa come quello della Figura 3, che è un automa nondeterministico. La dimostrazione per assurdo presentata precedentemente testimonia
che non c'è alcun automa deterministico che possa riconoscere lo stesso linguaggio. Al
di là dell'esempio pratico esiste un teorema che dimostra che un linguaggio è
riconoscibile da un automa deterministico automa di Büchi se e solo se il linguaggio è
limite di qualche altro linguaggio, ma per vedere nel dettaglio tale dimostrazione
rimandiamo alla lettura dello studio [26]
22
1.7 Proprietà di chiusura degli ω-linguaggi regolari
Chiusura rispetto all'unione: Se due linguaggi L1 e L2 sono due ω-linguaggi regolari,
quindi L1 , L2⊆  , allora L1∪ L2 è un ω-linguaggio regolare.
Dimostrazione: Se
L1 , L2⊆  sono due ω-linguaggi regolari allora esisteranno due
automi Büchi A1=(Σ, Q1, q01 ,δ1, F1) e A2=(Σ, Q2, q02 ,δ2, F2) che riconoscono tali
linguaggi, ossia L1=L(A1) e L2=L(A2). Per costruire l'automa di Büchi A=(Σ, Q, q0 ,δ,
F) tale che L(A) = L(A1) ∪ L(A2) l'idea è usare il prodotto cartesiano. Definiamo quindi
•
Q := Q1 × Q2
•
q0 := (q01,q02) ∈ Q
•
F := {(q1,q2) ∈ Q | q1 ∈ F1 oppure q2 ∈ F2 }
•
δ : Q × Σ → pow(Q)
((q1,q2),a)→ {(r,s)∈ Q | r∈ δ(q1,a), r∈ δ1(q1,a), s∈ δ2(q2,a) }
Dobbiamo dimostrare che effettivamente il linguaggio riconosciuto dall'automa
di
Büchi A è proprio l'unione dei due linguaggi di partenza. Sia w una parola
appartenente al linguaggio L1 ∪ L2. Sicuramente la parola apparteneva ad uno dei due
linguaggi (assumeremo w∈ L1 ). Dato che la parola apparteneva al primo linguaggio,
sicuramente c'era un run r1 in A1 su w tale che Inf(r1)∩F1 ≠ Ø. Se passiamo la parola w
come input di A, allora ci sarà un run del tipo (r,s)= (r0,r1,...., s0,s1...) per il quale
Inf(r,s)∩F ≠ Ø, perchè, per come è costruita la funzione di transizione e l'insieme degli
stati finali del nuovo automa, se esisteva q∈ F1 tale che q∈ Inf(r1), allora esisterà
nell'automa uno stato (q,q') tale che (q,q') ∈ Inf(r,s) e (q,q')∈ F . L'intersezione
Inf(r,s)∩F sarà, dunque, sicuramente non vuota e quindi w∈ L(A). In questo modo
abbiamo dimostrato che L(A1) ∪ L(A2) ⊆ L(A).
Viceversa se abbiamo una parola w che appartiene al linguaggio dell'automa A,
allora esisterà un run (r,s)=(r0,r1,...., s0,s1...) tale che Inf(r,s)∩F ≠ Ø. Esisterà dunque
uno stato (q,q') che appartiene all'insieme degli stati accettanti F che verrà visitato
23
infinite volte. Per come abbiamo definito F questo significa che o q∈ Inf(r)∩F1 oppure
che q'∈ Inf(s)∩F2. Dunque o Inf(r)∩F1 ≠ Ø oppure Inf(s)∩F2 ≠ Ø. Dunque la parola
appartiene a L1 ∪ L2 e L(A) ⊆ L(A1) ∪ L(A2).
Dall'unione dei due risultati possiamo dire che L(A) = L(A1) ∪ L(A2) e quindi il nuovo
linguaggio, dato che è rappresentabile da un automa di Büchi, è un ω-linguaggio
regolare.
Chiusura rispetto all'intersezione: Se due linguaggi L1 e L2 sono due ω-linguaggi
regolari, quindi L1 , L2⊆  , allora L1 ∩L2 è un ω-linguaggio regolare.
Dimostrazione: Se
L1 , L2⊆

sono due ω-linguaggi regolari allora esisteranno due
automi Büchi A1=(Σ, Q1, q01 ,δ1, F1) e A2=(Σ, Q2, q02 ,δ2, F2) che riconoscono tali
linguaggi, ossia L1=L(A1) e L2=L(A2). Il modo più naturale di pensare all'intersezione
fra i
due automi è considerare il prodotto dello spazio degli stati e lasciare che
entrambe le copie analizzino l'input simultaneamente. Per le parole finite, l'input era
accettato se ogni copia generava un run che raggiungeva uno stato finale. Per gli input
infiniti, costruire il prodotto comporta uno sforzo maggiore: tale problematica deriva
dal fatto non dobbiamo solo “raggiungere” gli stati accettanti, ma bisogna visitarli
infinitamente spesso. Non vi è alcun modo diretto per garantire che anche quando il run
visita infinitamente spesso uno stato accettante di A1, ne venga visitato infinite volte
anche uno di A2. Il problema risulta dunque essere concentrato nell'identificare qual'è
l'insieme degli stati da indicare nelle condizioni di accettazione che rappresentino il
comportamento dell'intersezione. L'idea per provare la chiusura rispetto all'intersezione
si basa su simulare i due automi contemporaneamente, presupporre che A1 vada in uno
stato appartenente F1 e e attendere continuando la simulazione finchè A2 non entra in
uno stato appartenente a F2. A questo punto si attende finchè A1 non rientri di nuovo in
uno stato appartenente a F1 e così via. Se lo stato di “attesa” viene visitato infinite
volte, allora siamo sicuri che la parola ha permesso di iterare infinitamente spesso sia
uno stato in F1 e che uno stato in F2 e quindi accettiamo la parola. Per gestire questa
operazione, introdurremo dei simboli che segnaleranno se stiamo attendendo di visitare
24
uno stato in F1 (con il valore 1) o se abbiamo visitato uno stato in F1 e stiamo
attendendo di visitare uno stato in F2 (con il valore 2) o se abbiamo visitato sia uno
stato in F1 che uno in F2 (con il valore 3). L'automa che realizza questo concetto è
costruito formalmente nel seguente modo:
•
Q := Q1 × Q2 × {1, 2, 3 }
•
q0 := (q01, q02 ,1 ) ∈ Q
•
F := F1 × F2 × 3
•
δ : Q × Σ → pow(Q)
((p1,p2, 1),a) → { ( δ1(q1,a),δ2(q2,a), 1) se e solo se δ1(q1,a) ∉ F1}
((p1,p2, 1),a) → { ( δ1(q1,a),δ2(q2,a), 2) se e solo se δ1(q1,a) ∈ F1}
((p1,p2, 2),a) → { ( δ1(q1,a),δ2(q2,a), 2) se e solo se δ2(q2,a) ∉ F2}
((p1,p2, 2),a) → { ( δ1(q1,a),δ2(q2,a), 3) se e solo se δ2(q2,a) ∈ F2}
((p1,p2, 3),a) → ( δ1(q1,a),δ2(q2,a), 1)
In tal modo l'automa di Büchi A= (Σ, Q, q0 ,δ, F) riconoscerà le parole appartenenti a
L(A1 ) ∩ L(A2) quindi L(A)= L1 ∩ L2
1.8 Il problema del vuoto
Decidere se, dato un automa di Büchi, questo automa riconosce almeno una
parola è un problema che in letteratura prende la definizione di problema del vuoto di
Büchi e, come vedremo successivamente, ricopre un ruolo determinante per la verifica
di sistemi reattivi le cui proprietà sono espresse tramite logiche temporali [43] [44] [6].
L'idea su cui si basa l'algoritmo per risolvere la decibilità del vuoto parte
dall'osservazione che, in base dalla definizione di automa di Büchi non-deterministico,
tale automa è non vuoto se esiste uno stato f∈ F tale che f è raggiungibile da uno stato
iniziale e che, dato che su di esso deve verificarsi una visita iterata, f possa essere
raggiunto anche a partire da se stesso. Dunque quello che si vuole realizzare è una
tecnica efficiente per trovare nel grafo che rappresenta l'automa un ciclo raggiungibile
25
che contenga almeno uno degli stati espressi F.
L'algoritmo considera l'automa come un grafo diretto e lo decompone nelle sue
vari componenti fortemente connesse, senza tener conto delle etichette sugli archi. Una
componente fortemente connessa C ottenuta da tale operazione è considerata non
banale se la sua intersezione con F è non vuota, quindi C ∩ F ≠ Ø, e C ha almeno un
arco. A questo punto, l'algoritmo cerca (tramite ad esempio una ricerca deep-first [17])
tutti i nodi a partire dai quali esiste un cammino che arrivi ad una delle componenti
fortemente connesse non banale individuate al passo precedente. Questa operazione
viene effettuata per trovare quale delle componenti fortemente connesse non banali è
raggiungibile. Se N è l'insieme di vertici che possiedono queste caratteristiche,
l'algoritmo non deve far altro che controllare se N ∩ Q0 = Ø, ossia se non è possibile
raggiungere un ciclo in cui vi è uno stato accettante a partire da uno degli stati iniziali
dell'automa. Se tale intersezione è vuota, allora anche L(A)= Ø e dunque diremo che
l'automa L(A) non accetta alcuna parola. La complessità di tale algoritmo, se usata la
ricerca deep-first, risulta essere O(|Q|+|δ|).
26
Capitolo 2
Le Logiche temporali
2.1 Dalle logiche classiche alle logiche temporali
Le logiche classiche da sempre sono state usate come strumento fondamentale
dell'informatica per rappresentare e analizzare il funzionamento di circuiti,
informazioni digitali e approfondire lo studio dei problemi di decisione relativi alla
teoria della complessità. Tuttavia le logiche di tipo ordinario presentano un grave e
insormontabile problema: la loro impossibilità di rappresentare il cambiamento del
tempo. Tali logiche, definite anche atemporali, possono solo esprimere dichiarazioni in
cui il tempo è un fattore costante e il valore di verità non può cambiare.
Se ci trovassimo invece a voler analizzare i meccanismi di sistemi reattivi, che
presentano tipicamente un comportamento non-deterministico e non sempre ripetibile,
abbiamo bisogno di logiche che possiedano degli strumenti adatti alla trattazione di
diverse modalità di verità. Potremmo volerci accertare che una proprietà venga “prima
o poi” verificata per ogni cammino, oppure che da un certo punto in poi per ogni
possibile branch una particolare caratteristica venga “sempre” soddisfatta. E' evidente
che gli operatori forniti da logiche ordinarie non possono modellare tali concetti. A tale
scopo sono state sviluppate le logiche modali, logiche che permettono di studiare vari
modelli di verità[28].
La logica temporale è un particolare tipo di logica modale nella quale la verità di
una asserzione può variare rispetto al tempo. L'obiettivo che si prefiggono le logiche
temporali [32] [23] è quello di formalizzare il concetto del tempo, ma, data l'ampiezza
del problema, esistono numerose classi di logiche temporali. Possiamo avere logiche
temporali come logiche modali, in cui il tempo è implicito nella semantica, oppure
logiche temporali come logiche di predicati, in cui il tempo è un fattore esplicito. Non
basta parlare di “tempo” generico, poiché ogni logica possiede una sua semantica
dipendente dal flusso del tempo, che può essere ad esempio, finito, infinito, branching,
27
lineare, etc... , e dall'unità di tempo, che potrebbero essere eventi, intervalli, punti, etc...
[40].
Le logiche temporali trovano numerose applicazioni in informatica, in
particolare per l'espressione di caratteristiche legate a sistemi reattivi, verifica di
hardware e database temporali. Le formule che apparterranno alle logiche temporali che
verranno presentate in questo capitolo possiedono, inoltre, la caratteristica di poter
essere elaborate tramite degli algoritmi (e quindi da tool automatici) per controllare
proprietà di un sistema il cui comportamento può essere modellato tramite specifiche
strutture dette di Kripke. Gli algoritmi che permettono di decidere se una struttura M di
Kripke rispetta una formula φ possono basarsi su vari tipi di approcci, come i tableux
[27] e metodi basati su computazioni a punto fisso [19], ma ci soffermeremo su quelli
basati sulla teoria degli automi, che sfruttano la connessione fra logiche temporali e
proprietà di linguaggi regolari su stringhe finite e infinite.
2.2 Il modello
Come in altre scienze, un modello è un'astrazione atta a rappresentare un
particolare tipo di fenomeno o condizione. Estrapolare un modello a partire da una
realtà concreta è un processo che richiede una determinata conoscenza ed esperienza
del problema ed è richiesta una particolare attenzione alla scelta dell'approccio da
impiegare per poter costruire una semplificazione del contesto che vogliamo modellare,
valorizzando i dati importanti e necessari ed eliminando invece quelli superflui per
l'analisi. Un modello, infatti, è valido o meno a simulare una realtà in relazione
all'obiettivo che vogliamo perseguire solo se è effettivamente utile a tale obiettivo. Se
vogliamo analizzare il comportamento di una stampante in rete è utile conoscere la
topografia della rete, quali sono le postazioni che possono inviare documenti da
stampare, come vengono gestite le chiamate concorrenti, mentre invece potrebbe essere
superfluo tenere traccia del livello dell'inchiostro o del tipo di documento da processare.
Dunque
per poter comprendere
quale metodologia
applicare per la
rappresentazione di un modello adatto a supportare specifiche temporali, bisogna
28
analizzare attentamente la realtà che dobbiamo rappresentare e gli scopi che vogliamo
perseguire.
I sistemi che solitamente consideriamo come oggetto della verifica vengono
definiti sistemi reattivi, ossia sistemi il cui stato cambia in relazione all'occorrenza di
determinati eventi. Un modello per la verifica formale di tali sistemi dovrebbe, dunque,
porsi come primo obiettivo quello di catturare lo stato del sistema. Lo stato dovrebbe
essere una sorta di istantanea che coglie i valori delle sue variabili in un particolare
istante di tempo. La seconda informazione determinante di cui tenere traccia è come le
varie caratteristiche e proprietà del sistema cambiano durante il tempo quando occorre
un'azione: è necessario esplicitare a partire da ogni stato quali azioni sono possibili e
quali modifiche nel sistema sono associate ad ogni azione prevista. La coppia stato
prima di effettuare l'azione e stato dopo aver effettuato l'azione determina quella che
viene definita transizione del sistema. Tutti i vari calcoli e operazioni di un sistema
reattivo possono essere definite in termini delle sue transizioni e ogni singolo calcolo è
un'infinita sequenza di stati dove ogni stato è ottenuto a partire dal precedente
attraverso una delle transizioni possibili.
2.2.1 Le strutture di Kripke
Sulla base delle considerazioni precedentemente effettuate, per modellare un
sistema reattivo viene impiegato un tipo di macchina non-deterministica a stati finiti
chiamata struttura di Kripke. Essa è sostanzialmente un grafo in cui ogni nodo
rappresenta uno stato raggiungibile dal sistema e ogni arco rappresenta una transizione
di stato. Ogni cammino nella struttura di Kripke modella una computazione del sistema.
Una funzione di etichettamento mappa ogni nodo a ogni insieme di proprietà che sono
valide nel corrispondente stato.
Definiamo una struttura di Kripke in maniera più formale.
29
Sia AP un insieme di proposizioni atomiche. Una struttura di Kripke K su AP è
una quadrupla
K = (S, S0, R, lab)
dove
•
S è un insieme finito di stati
•
S0 è l'insieme dei stati iniziali e S 0⊆S
•
R⊆S ×S
è la relazione di transizione. ∀ s ∈S , ∃s ' ∈ S
tale che
 s , s ' ∈ R
•
lab : S → 2AP funzione di etichettamento che ad ogni stato associa l'insieme di
proposizioni atomiche che è vero in quello stato
Un run o path ρ è una successione di stati s0, s1, s2, …, sn, … dove fra lo stato i-
esimo e l'i+1-esimo esiste una transizione appartenente all'insieme R. Il linguaggio della
struttura di Kripke non sarà altro che l'insieme di tutti quei path che rispettano la
relazione di transizione
L  K ={L∣∀ i∈ℕ , i , i1∈R}
Nonostante questi modelli siano semplici tuttavia sono abbastanza espressivi da
catturare gli aspetti e il comportamento durante il trascorrere del tempo dei sistemi
reattivi. Inoltre le strutture di Kripke
sono utilizzate per interpretare le logiche
temporali.
30
2.3 Operatori temporali
Per esprimere concetti come “sempre”, “mai”, “alla fine” le logiche temporali
hanno la necessità di introdurre speciali operatori che, combinati con gli altri operatori
booleani
¬ NOT ,∨OR ,∧AND generino le varie formule.
Gli operatori temporali sono:
– X (il prossimo, subito dopo) la proprietà deve essere vera nello stato successivo
a quello su cui viene chiamato il next (tale operatore a volte si trova anche scritto
con la lettera N)
– F (in futuro, dopo) la proprietà deve essere presente in qualche stato successivo
nel cammino a partire dal nodo dove viene invocato l'operatore
– G (sempre) la proprietà espressa con G deve essere vera in ogni stato del path
– U (finchè) è un operatore binario. Finchè non compare uno stato in cui è vera la
seconda proprietà, tutti gli stati precedenti in quel cammino devono avere la
prima proprietà vera.
– R (rilascia) la seconda proprietà deve sempre vera fino alla prima posizione in
cui la prima formula è vera (compresa). Se la prima proprietà non è mai
soddisfatta, R è vera anche se la seconda proprietà è sempre vera in ogni stato
del path (Il simbolo R viene usato raramente e spesse volte non compare nelle
descrizioni delle logiche).
– W (debole finchè) è un operatore binario. Ha una semantica simile a quella
dell'until, con la differenza che non è richiesto che occorra la condizione di
fermata. Se la proprietà alla sinistra dell'operatore non è mai vera, il weak until è
soddisfatto se la proprietà alla sinistra dell'operatore e vera in ogni stato del path.
(Anche il simbolo W è usato raramente e compare definito solo da alcuni autori)
31
Tramite gli operatori appena definiti possiamo esprimere tutta una serie di
specifiche che descrivano condizioni verificabili su un sistema reattivo. Tuttavia, come
annunciato in precedenza, l'intero comportamento dei sistemi reattivi non può essere
considerato con singoli path, ma deve essere rappresentato da dei veri e propri alberi.
Consideriamo una semplice proprietà Xφ, richiesta in un nodo i da cui si dipartono
diversi archi di transizione. Quando è soddisfatta tale formula? Quando tutti gli stati
raggiungibili in una transizione a partire da i soddisfano φ? O basta che in uno solo di
questi φ sia vera? I soli operatori temporali, dunque, non sono sufficienti a specificare
in maniera non ambigua dove e come vogliamo che le proprietà siano soddisfatte.
Abbiamo bisogno di operatori sui cammini che permettano di esplicitare in maniera
univoca se vogliamo che proprietà da verificare in momenti successivi siano controllate
su un solo cammino o su tutti i cammini.
Gli operatori sui cammini sono i seguenti:
– E (operatore esistenziale) la formula preceduta dall' E è vera solo se è vera su un
cammino
– A (operatore universale) la formula preceduta dall'A è vera solo se è vera per
ogni cammino
L'uso di tali operatori può modificare sostanzialmente il significato delle formule. Ad
esempio scrivere la proprietà
G (b → Fa)
significa sempre ogni volta che è verificata b allora dopo anche a è verificata.
Se invece richiedessi che il sistema soddisfi la proprietà seguente
AG (b → EFa)
richiederei che per ogni cammino ogni volta che b è verificata allora esiste un cammino
per il quale anche a è verificata.
Se a fosse una proprietà legata all'uscita potremmo dire che fra le due formule c'è
la differenza che esiste nel dire “devo sempre finire con un'uscita” e “in ogni momento
ho la possibilità di uscire”.
32
Un piccolo esempio grafico sulle differenze generate dall'uso degli operatori sui
cammini è presentato nella Figura 4 (pagina seguente) allo scopo di rendere più
facilmente comprensibile le richieste imposte dalle specifiche che ne sfruttano le
caratteristiche
Figura 4: Differenze provocate dall'uso degli operatori sui cammini
La tabella nella pagina seguente, invece, riassume tutti gli operatori visti
precedentemente, divisi per tipologie, con il loro nome e simbolo, testuale e non.
Un'ultima cosa da notare è che le logiche temporali che usano questi simboli tengono
traccia del tempo in maniera qualitativa ma non quantitativa. Possiamo richiedere che
una condizione venga soddisfatta “dopo” ma non entro quanto tempo.
Nei prossimi paragrafi descriveremo in dettaglio le caratteristiche di tre fra le
principali logiche temporali: CTL*, CTL e LTL
33
Nome
Testuale
Operatore
Operatori Unari
Next
Xφ
Future
Fφ
Globally
Gφ
Simbolico
Spiegazione
φ è verificata nello stato immediatamente
successivo
φ alla fine è verificata (da qualsiasi stato
facente parte del successivo cammino).
φ deve essere verificata nell'intero
cammino successivo allo stato dove G è
invocata
Operatori Binari
Until
φUψ
Release
φRψ
Weak Until φ W ψ
φW ψ
ψ è verificata nell'attuale o una successiva
posizione e φ deve essere vera fino a tale
posizione. Nello stato in cui ψ è vera φ
non deve più esserlo
φ rilascia ψ se ψ è vera fino alla prima
occorrenza nella quale φ è vera (o sempre
se tale occorrenza non esiste)
ψ è verificata nell'attuale o una successiva
posizione e φ deve essere vera fino a tale
posizione. Nello stato in cui ψ è vera φ
non deve più esserlo. Se ψ non è mai
verificata, la proprietà è vera anche se φ
risulta essere vera in ogni stato del path
Operatori sui cammini
All
Aφ
Exist
Eφ
φ è verificata su tutti i cammini a partire
dallo stato corrente
Esiste almeno un cammino a partire dal
nodo corrente che soddisfa φ
Tabella 1: Operatori logiche temporali
34
2.4 La superclasse CTL*
La logica CTL* può essere intesa come una superclasse di CTL e LTL. La
differenza fra le varie logiche non risiede sostanzialmente nei simboli usati, ma nel
modo in cui tali simboli possono essere “accoppiati” per generare le varie espressioni.
L'alfabeto di tutte e tre le logiche sarà lo stesso
ALFABETO
a,b,c,...
Proposizioni atomiche
p1, p2, p3, ...
˄,˅,¬,→
Operatori booleani
U, X, G, F
Operatori temporali
A, E
Operatori sui cammini
(,)
Parentesi e punteggiatura
True, false
Costanti
2.4.1 Sintassi
La sintassi di CTL* che genera il linguaggio CTL* è una grammatica contextfree che è caratterizzata dalle seguenti regole:
φ := p | ¬φ | φ˄φ | φ˅φ | φ→φ | Xφ | φUφ | Gφ | Fφ | Aφ| Eφ | (φ) | true | false
Dove p è una singola formula atomica presa dall''insieme. Una formula φ che usa solo
operatori temporali viene definita formula di cammino. Una formula che usa tutti gli
operatori è detta formula di stato.
Tuttavia alcuni simboli possono essere eliminati “simulando” il loro
comportamento tramite equivalenze ottenute con un numero più ristretto di simboli.
- true, false: ricavandoli da
true ≡ p ˅ ¬ p
35
false ≡ p ˄ ¬ p ≡ ¬ true
-G, F: ricavandoli da
Fφ ≡ true U φ
Gφ ≡ ¬ F ¬ φ
-˅, ¬ : usando le Leggi di De Morgan
φ ˅ φ ≡ ¬( ¬ φ ˄ ¬ φ)
φ→ φ ≡ ¬ φ ˅ φ ≡ ¬ (φ ˄ ¬ φ)
-A sostituendola con
A φ ≡ ¬E¬ φ
Effettuate tali eliminazioni si può ricavare una sintassi ridotta:
φ := p | ¬φ | φ˄φ | Xφ | φUφ | Eφ | (φ)
2.4.2 Semantica
Prima di definire la semantica delle espressioni in CTL ricordiamo che
definiremo tali formule su una struttura di Kripke M con:
•
M = (S, S0, R, Lab)
•
Lab: S → 2AP
•
R⊆Q X Q
•
Vs = true se p ∈labs 
false se p ∉lab s 
•
run σ = s0, s1, s2, …, sn, … ∀ i s i , s i1 ∈ R
Prendiamo un qualsiasi run σ. La notazione A, σ, i ╞ φ diremo che è vera se in una
struttura A, in un run σ e al tempo i arrivo a φ. Se il contesto lo permette, A si può
omettere e si può usare la notazione σ, i ╞A φ o σ, i ╞ φ.
36
La semantica sarà
– σ, i ╞ p ↔
p ∈lab σ i
– σ, i ╞ ¬ p ↔ NOT(σ, i ╞ p)
– σ, i ╞ φ1˄φ2 ↔ σ, i ╞ φ1 e σ, i ╞ φ2
– σ, i ╞ φ1˅φ2 ↔ σ, i ╞ φ1 oppure σ, i ╞ φ2
– σ, i ╞ φ1→φ2 ↔ se σ, i ╞ φ1 allora σ, i ╞ φ2
– σ, i ╞ Xφ ↔ σ, i+1 ╞ φ
– σ, i ╞ Gφ ↔ σ, j ╞ φ
– σ, i ╞ Fφ ↔
∀ j ≥ i+1
∃ j ≥ i+1 tale che σ, j ╞ φ
– σ, i ╞ φ1Uφ2 ↔ ∃ j con j≥ i tale che σ, j ╞ φ2 ∀ h tale che i ≤ h < j allora σ,
i ╞ φ1
– σ, i ╞ Eφ ↔ ∃σ ' ∈Run  A tale che σ(0) … σ(i)=σ'(0) … σ'(i) e σ', i ╞ φ
– σ, i ╞ Aφ ↔ ∀ σ ' ∈Run A tale che σ(0) … σ(i)=σ'(0) … σ'(i) e σ', i ╞ φ
– σ, i ╞ (φ) ↔ σ, i ╞ φ
2.5 CTL e LTL
Computation tree logic (CTL) è una logica braching-time come CTL*, ossia il modello
che rappresenta è quello di una struttura ad albero in cui il futuro non è determinato. Vi
sono differenti cammini nel futuro, ognuno dei quali potrebbe essere quello che si
concretizza. A differenza però della superclasse (da cui eredita l'alfabeto) nella sintassi
gli operatori sui cammini non possono essere accoppiati a piacimento ma devono
seguire le seguenti regole:
φ := p | (¬φ) | (φ˄φ) | (φ˅φ) | (φ→φ) | AXφ | A(φUφ) | AGφ | AFφ | EXφ | E(φUφ) |
EGφ | EFφ | true | false
dove p è un elemento appartenente all'insieme di formule atomiche.
37
La limitazione sulle modalità di aggregazione e concatenazione delle varie
formule, insita nella evidente regola di dover associare un solo operatore sui cammini
seguito da un operatore temporale, rende facilmente intuibile che vi sono formule in
CTL* che non possono essere espresse in CTL.
Linear temporal logic (LTL), invece, è una logica modale temporale che
permette di avere riferimenti al tempo. Pur permettendo di esprimere i concetti tipici
delle logiche temporali tramite tutti gli operatori temporali, nell'alfabeto e la sintassi
non vi è l'uso di alcun tipo di quantificatore sui cammini. Nelle formule LTL è
implicitamente prefisso l'operatore universale sui cammini. La logica dunque mira a
studiare il comportamento di un cammino o di un insieme di cammini (se ve ne sono
più di uno) e in ogni istante si può avere solo un possibile futuro.
La sintassi, privata degli opportuni quantificatori, sarà la seguente:
φ := p | ¬φ | φ˄φ | φ˅φ | φ→φ | (φ) | Xφ | φUφ | Gφ | Fφ | true | false
Viste le restrizioni sull'accoppiamento degli operatori per generare espressioni
valide nelle varie logiche, le seguenti formule vogliono essere degli esempi volti a
evidenziare più concretamente le differenze fra le possibili costruzioni.
Formula CTL* che non è né in LTL né in CTL: EX(p)˅AFG(p)
Formula CTL che non è in LTL: E(pUq)
Formula LTL che non è in CTL: AFG(p)
Formula presente sia in LTL che in CTL: AF(p)
2.6 Model checking LTL
Le formule LTL rappresentano un insieme di run infiniti che soddisfano una
determinata formula. Come abbiamo visto in precedenza anche gli automi di Büchi
accettano insiemi di sequenze infinite. E' possibile costruire un automa ad hoc che
accetti tutte e solo le infinite stringhe rappresentate da una formula LTL e ridurre il
problema del model checking LTL a un problema su automi di Büchi.
38
Dato un programma P e una formula φ espressa tramite LTL che verifica tutte le
computazioni infinite di P che soddisfano φ, il metodo più semplice consisterebbe nel
verificare che tutti i comportamenti del sistema siano desiderabili, ossia che, costruito
un automa di Büchi che riconosca φ (che chiameremo Aφ) e uno che rappresenti il
modello del sistema (useremo la notazione AP), garantire che L(AP) ⊆ L(Aφ). Pur
essendo tale tecnica la soluzione apparentemente più immediata a livello intuitivo,
tuttavia decidere l'inclusione fra linguaggi accettati è un problema appartenente a NP.
Il problema dell'inclusione può essere facilmente aggirato poichè
L  AP ⊆L Aφ ⇔ L  A P ∩L Aφ =∅
dove Aφ è l'automa di Büchi complementare di Aφ e accetta il linguaggio Σω \ L(Aφ).
Questa tecnica, che risulta essere applicabile, introduce una nuova problematica, poiché
in generale la costruzione di
Aφ è quadraticamente esponenziale, ossia se Aφ ha n stati
2
allora Aφ avrà c n con c1 stati.
Per limitare l'esplosione degli stati nella creazione dell'automa che rappresenta la
formula e osservando che L(A¬φ)= L  Aφ  il metodo efficiente di model checking LTL
consterà dei seguenti passi:
•
Costruire l'automa di Büchi per ¬φ = A¬φ
•
Costruire l'automa di Büchi per il modello del sistema = AP
•
Verificare se L  A P ∩L A¬φ =∅
Ovviamente i run in comune fra A¬φ e AP violeranno le specifiche.
Se abbiamo dei processi Pi, con 1 ≤ i ≤ n, e il cui sistema di transizioni è
rappresentato da
•
uno spazio degli stati Qi
•
un insieme di azioni Σi
•
un nodo di partenza v0i appartenente a Qi
39
•
una funzione di transizione non-deterministica δi : Qi × Σi → 2Qi
per ottenere un automa che ne rappresenti il funzionamento concorrente derivato
dall'esecuzione dei processi Pi basta effettuare il prodotto delle transizioni derivate da
ogni sistema. Il nuovo automa che chiameremo AP = (Σ, Q, q02 ,δ2, F2) dove:
•
V = Π Vi, ossia lo spazio degli stati è il prodotto cartesiano degli spazi degli stati
dei singoli processi Pi
•
Σ = U Σi, ossia l'insieme delle azioni è dato dall'unione di tutte le possibili azioni
effettuabili sui processi Pi
•
v0=(v01, v02, … v0n)
•
δ è la funzione di transizione tale che (q1', q2', …. ) ∈ δ((q1, q2, …. ), a) se e solo
se
- qi' ∈ δ(q1, a) per ogni i tale che a ∈ Σi e
- vi' = vi per ogni i tale che a ∉ Σi
ossia sincronizziamo le transizioni sulla base dello stesso simbolo: laddove il simbolo
faceva transire uno stato, allora manteniamo tale transizione. Gli stati dei processi per
cui quel simbolo non faceva parte dell'insieme delle azioni possibili, restano
ovviamente invariati. Per vederlo come un automa di Büchi, dobbiamo infine decidere
qual'è l'insieme degli stati accettanti. Dato che l'obiettivo è verificare le proprietà su P e
l'automa è reso necessario solo per formalizzare tutte le sue possibili esecuzioni,
considereremo l'insieme degli stati accettanti come tutto l'insieme V.
Per trasformare una formula LTL [46] abbiamo bisogno di individuare quali sono
le sequenze σ : N → 2AP che soddisfano la formula φ su un insieme di proposizioni AP.
Possiamo individuare tali sequenze etichettandole con le sottoformule di φ in maniera
tale che rispettino la semantica degli operatori LTL (per comodità di rappresentazione
considereremo solo la semantica ridotta e negazioni delle formule applicate solo sulle
formule atomiche). Il primo passo è definire un insieme di sottoformule della formula
φ che sono necessarie e a tale scopo definiamo la chiusura di φ. Con Cl(φ) intendiamo:
40
•
φ ∈ Cl(φ)
•
se ¬φ1 ∈ Cl(φ) allora φ1∈ cl(φ)
•
se φ1˅φ2 ∈ Cl(φ) allora φ1,φ2∈ cl(φ)
•
se Xφ1 ∈ Cl(φ) allora φ1∈ cl(φ)
•
se φ1Uφ2 ∈ Cl(φ) allora φ1,φ2∈ cl(φ)
Il passo successivo è definire un insieme di regole che una etichettatura di
chiusura valida τ : N →2cl(φ) per una sequenza σ : N → 2AP deve soddisfare. Se una
formula φ1 ∈ cl(φ) etichetta una posizione i (quindi φ1∈ τ(φ1)) allora la sequenza σi
soddisfa tale formula σ, i ╞ φ1. Consideriamo l'etichettatura di chiusura τ: N →2cl(φ) su
di una sequenza σ : N → 2AP . Per essere valida deve soddisfare le seguenti regole per
ogni i ≥ 0:
•
false ∉ τ(i)
•
per ogni p ∈ P, se p ∈ τ(i) allora p ∈ σ(i) e se ¬p ∈ τ(i) allora p ∉ σ(i)
•
se φ1˅φ2 ∈ τ(i) allora φ1∈ τ(i) o φ2∈ τ(i)
•
se Xφ1∈ τ(i) allora φ1∈ τ(i+1)
•
se φ1Uφ2∈ τ(i) allora o φ2∈ τ(i) oppure φ1∈ τ(i) e φ1Uφ2∈ τ(i+1)
•
se φ1Uφ2∈ τ(i) allora esiste un valore j ≥ i tale che φ2∈ τ(j)
In tal modo per ogni formula φ'∈ Cl(φ) e i ≥ 0 se φ'∈ τ(i) allora σ, i ╞ φ'.
La costruzione dell'ω-automa generalizzato di Büchi che accetta tutte le
sequenza che soddisfano una formula φ è un automa Aφ=(Σ, Q, Q0 ,δ, F) dove:
•
Σ= 2AP
•
Q è l'insieme di tutte le possibili etichette, cioè tutti i sottoinsiemi q ∈ 2cl(φ) tali
che l'etichetta false non appartiene a q e se φ1˅φ2 appartengono all'insieme,
allora solo una delle due etichette appartiene al sottoinsieme q
•
t ∈ δ(q,a) se e solo se
41
- Per tutti i p ∈ P se p ∈ q allora p ∈ a
- Per tutti i p ∈ P se ¬p ∈ q allora p ∉ a
- Se Xφ1∈ q allora φ1∈ t
- Se φ1Uφ2∈ q allora o φ2∈ q oppure φ1∈ q e φ1Uφ2∈ t
•
Q0 = {q ∈ Q | φ∈ q}
•
F={F1, F2, …, Fm} dove Fi = {q ∈ Q | ei, φi ∈ q oppure ei ∉ q } dove ei sono le
formule di Until che compaiono in nelle varie formule φi presenti in Cl(φ).
Per evitare l'esplosione degli stati, si può ottimizzare l'automa applicando diversi
tipi di approcci, come impiegare tecniche di riscrittura della formula per ridurne la
taglia o provare a minimizzare il numero degli stati, delle transizioni e degli stati
accettanti, ad esempio omettendo le transizioni ridondanti, eliminando le parti
dell'automa che non sono raggiungibili e identificando e accorpando gli stati equivalenti
42
Capitolo 3
Una logica per rappresentare chiamate ricorsive: CARET
3.1 Perchè introdurre CARET
Finora, parlando di sistemi reattivi, abbiamo sempre parlato di modelli che
reagiscono ad eventi cambiando il loro stato. Esiste però un'altra caratteristica della
programmazione che fino a questo momento non è stata considerata: la possibilità di
effettuare chiamate ricorsive. Questa caratteristica è tipica dei linguaggi di
programmazione sequenziali imperativi, ma anche della strutturazione dei sistemi in
generale, in cui si tende individuare possibili componenti logicamente indipendenti che
interagiscono fra di loro attraverso le chiamate. Nella teoria degli automi, meccanismi
di questo tipo possono essere modellati attraverso macchine ricorsive a stati finiti, la cui
caratteristica principale consiste nell'avere un insieme di componenti, chiamate moduli,
che interagiscono fra loro tramite delle chiamate di tipo call e return. L'insieme degli
stati è costituito da nodi (che sono stati “atomici”) e box (che rappresentano un altro
modulo). Ogni modulo ha un'interfaccia, ossia stati speciali di entrata e di uscita. Gli
archi connettono nodi a nodi o nodi a box. Un arco entrante in una box modella la call
al modulo esterno associato a quella box e l'arco uscente da una box corrisponde al
return da quel modulo.
Nonostante sia stato dimostrato che logiche come LTL, abbondantemente usate
per espressioni di proprietà su sistemi reattivi flat [32][27][19], possano essere usate
anche su macchine ricorsive a stati finiti e automi pushdown[11][7][1][20], tuttavia la
capacità espressiva della logica resta inalterata e le proprietà che possiamo verificare
tramite formule scritte in LTL sono intrinsecamente collegate ai costrutti delle proprietà
regolari. Condizioni che associano chiamate e ritorni, come ad esempio richiedere che
se una proprietà p è verificata prima di una chiamata allora dopo il ritorno da quella
chiamata deve valere anche q, non sono concetti esprimibili attraverso gli operatori e la
43
sintassi offerta da LTL, nonostante siano caratteristiche interessanti da verificare,
soprattutto nell'ambito del linguaggi imperativi e object-oriented e nella verifica di
diversi linguaggi moderni impiegati per la specifica delle interfacce.
Tre studiosi, Alur, Etessami e Madhusudan [2], hanno proposto un nuovo tipo di
logica che fornisce gli strumenti per formalizzare determinate proprietà richieste fra call
e return e può dunque esprimere specifiche per analizzare la correttezza parziale o
totale nel rispetto delle pre e post condizioni e controlli sullo stack.
3.2 Le computazioni strutturate
Le formule della logica CARET hanno bisogno di introdurre una ulteriore
notazione sugli stati per poter essere interpretate correttamente: infatti quando
descriviamo una parola, ogni elemento della parola non deve solo indicarci lo stato del
sistema e le funzioni che sono valide, ma deve anche possedere informazioni
riguardanti la tipologia dell'azione intrapresa, per poter comprendere come sono
annidate le varie operazioni di chiamata e risposta e creare relazioni consistenti fra esse.
Dunque tali formule vengono interpretate su computazioni strutturate, intese come
delle infinite sequenze di stati dove ad ogni stato non vengono solo assegnati valori alle
proposizione atomiche, ma vengono accompagnati da etichette scelte fra i termini
call/ret/int. Questi simboli descrivono la tipologia del nodo e riescono a individuare
rispettivamente call verso moduli, return da moduli e nodi interni. In questo modo è
possibile ricostruire il corretto annidamento delle chiamate e individuare in ogni
momento la componente in cui ci troviamo in qualsiasi punto dell'input.
Data la natura ricorsiva delle chiamate, in una computazione strutturata abbiamo
necessità di comprendere alcune nozioni che sono o del tutto nuove o che assumono un
significato diverso da quello a cui siamo abituati quando parliamo invece di modelli di
computazione non ricorsivi. Ad esempio il concetto di successore assume delle
problematiche completamente differenti. Quando parliamo di modelli non ricorsivi, un
successore è tipicamente un nodo che viene “dopo” il nodo attuale. In modelli ricorsivi
questo “dopo” come deve essere interpretato? Non dobbiamo dimenticare che un nodo
44
può essere anche una chiamata. Se il vertice è una call, il suo successore qual'è?
Bisogna considerare il nodo che viene immediatamente dopo, dunque appartenente al
nuovo modulo invocato, o il nodo successivo nel modulo, quindi effettivamente il
vertice dove arriviamo quando usciamo dalla invocazione della box?
Inoltre dovremmo fornire una funzione che ci permetta di per poter risalire, da
un qualsiasi elemento, al nodo da cui è partita la chiamata prima di giungere a tale
elemento. Questo accorgimento serve principalmente per poter esprimere legami fra i
vari moduli, come ad esempio imporre che per entrare in un modulo B dobbiamo averlo
invocato a partire da un modulo A.
Procedendo ad una definizione formale dei concetti precedenti, viene fissato un
insieme finito di simboli Σ e tale alfabeto viene aumentato prevedendo la possibilità di
accoppiare alle lettere dell'alfabeto le etichette call, ret e int. Il nuovo alfabeto ottenuto
sarà dunque

=×{
call, ret, int } . I simboli di tale alfabeto saranno delle coppie del
tipo (σ,call), (σ,ret), (σ,int). Intuitivamente call verrà associato ai nodi che hanno
effettuato invocazioni a moduli, con ret saranno etichettati i return da moduli e con int
verranno individuati tutti quegli stati in cui vengono effettuate azioni interne allo stesso
modulo.
Dato un intero i ≥0, con la notazione αi ci riferiremo a l'i-simo simbolo di una
parola infinita α e con αi indicheremo il suffisso di α partendo dall'i-esimo simbolo.
Per ogni parola su
 viene definita in maniera formale l'associazione fra le

chiamate e i rispettivi ritorni. Dato αi=(σ,call), e un return αj=(σ',ret) dovremmo
indicare in che modo leghiamo il ritorno j alla chiamata i. L'operazione si basa su un
conteggio a partire dalla posizione i-esima delle varie call e dei vari return esistenti
nella stringa: se il numero dei ritorni è maggiore del numero delle chiamate, significa
che c'è un ret che non è stato ancora collegato ad alcuna call, dunque si prende la prima
posizione dove è presente un ritorno “libero” e la si associa alla chiamata nel simbolo iesimo. Tale meccanismo viene realizzato, anche se in forma generalizzata, dalla
funzione Rα che, invocata per un intero i, lega ad esso il primo ritorno dopo i presente
nella stringa e non ancora assegnato ad alcuna chiamata. Matematicamente, la funzione
opera in maniera tale che, se c'è un valore j' tale che j' è più grande di i ed è un return ed
il numero di chiamate e ritorni presenti nella sottostringa αi+1 αi+2.... αj'-1 è uguale,
45
allora prenderemo il più piccolo j' (che sarà il valore j) e il valore restituito dalla
funzione sarà Rα(i)= j. Se il numero dei ritorni è inferiore a quello delle chiamate
oppure uguale ma non è presente alcun elemento j' disponibile, allora non esisterà un
ritorno per la chiamata, dunque Rα(i)= ⊥. Da notare che Rα viene definita per ogni
intero, non ponendo nessuna condizione sul fatto che l'i-esimo simbolo sia del tipo
(σ,call): restituisce solo il primo return non associato, ma, intuitivamente, se il simbolo
su cui viene invocata la funzione rappresenta una chiamata, ci darà esattamente il
corrispondente ritorno, se presente.
Le diverse nozioni di successore e di caller necessitano di tre rappresentazioni
differenti e distinguibili:
– succg, denominato successore globale, rappresenta il concetto della funzione di
successore nella sua accezione più comune, ossia il simbolo seguente. Dunque
succgα(i)=i+1
– succa, denominato successore astratto, invece, identifica il prossimo successore
locale. Se il simbolo attuale è una chiamata, salta tutti i simboli finchè il token
non ritorna nel modulo in cui la funzione succa è stata invocata, per poi restituire
il vertice in cui si è ritornati (quindi succaα(i)=Rα(i) per i=(σ,call)). Se la
funzione di successore astratto viene invocata su in simbolo etichettato con ret,
ovviamente l'uscita dal modulo conseguente non permetterà di visitare altri
simboli di quel modulo e la funzione restituirà il valore ⊥ ( succaα(i)=⊥ per
i=(σ,ret)).
In tutti gli altri casi si comporta come la funzione globale (
succaα(i)=i+1 per i=(σ,int))
– succ-, inteso come caller, ci dice qual'è la chiamata precedente più “vicina” e
ancora non “chiusa” con un ritorno rispetto alla posizione attuale. Se esiste un
simbolo in cui precedentemente è stata effettuata una chiamata e il suo return è
successivo alla posizione attuale oppure non è presente nella parola, allora la
funzione restituirà il più grande indice fra tutti quelli che rispettano tale
condizione. Formalmente se esiste un indice un intero j' tale che j'< i e αj' è una
chiamata tale che Rα(j') > i oppure Rα(j')=⊥ allora succ-α(i) = j dove j è il più
grande indice fra tutti i possibili j'. In tutti gli altri casi succ-α(i) =⊥ .
46
3.3 Il modello: RSM
Il particolare contesto applicativo in cui vuole essere collocata una logica come
CARET impone l'adozione di un modello che sia adeguato alla rappresentazione delle
chiamate annidate. Verranno dunque utilizzate le macchine ricorsive a stati finiti
(abbreviato RSM).
Un RSM S su un insieme di proposizione AP è una tupla
S=(M, start,{Sm}mєM)
dove:
•
M è un insieme finito di nomi di moduli
•
start è un insieme di nodi di partenza definiti come start ⊆ Um∈M Nm
•
Sm è il modulo di ogni m∈M
Un modulo Sm è una tupla
(Nm, Bm, Ym, Enm, Exm, δm, ηm)
dove:
•
Nm: un insieme finito e non vuoto di nodi
•
Bm: un insieme finito di box
•
Ym: è una funzione di etichettamento Ym: Bm→M che associa ad ogni box un
modulo
•
Enm: è un insieme non vuoto di nodi di entrata
•
Exm: è un insieme non vuoto di nodi di uscita
•
N
δm: funzione di transizione m : N m∪Retns m  2
•
ηm: funzione di etichettamento
∪Callsm
m
ηm: Vm → 2AP che associa un insieme di
47
proposizioni ad ogni vertice, sia esso nodo interno, chiamata o ritorno.
Un discorso particolare è necessario per specificare come opera la funzione di
transizione. Data la possibilità di effettuare chiamate ricorsive, le transizioni possono
essere effettuate da nodo a nodo o da nodo a box, quindi invocazioni di altri moduli, o
da box a nodo, ossia ritorni da moduli che erano stati invocati precedentemente.
Dunque definiremo due nuovi insiemi:
Callsm={(b,e) | b∈B m , e ∈EnY
m
b 
}
dove ogni elemento (b,e) è una chiamata del modulo m al modulo m', che corrisponde
all'etichetta b
(quindi Ym(b)=m'), ed e è uno dei nodi di entrata di m' (ossia e ∈Enm ' ).
Retnsm={(b,x) | b∈ B m , x ∈ExY
m
 b
}
dove ogni elemento (b,x) è un ritorno del controllo da un modulo m', che corrisponde al
modulo associato all'etichetta b (quindi Ym(b)=m'), al modulo m quando viene chiamata
l'uscita nel modulo m' dal nodo x di uscita (ossia x ∈Ex m ' ).
Specificati questi due nuovi insiemi risulta essere dunque più intuibile come
opera la funzione di transizione e quali sono le motivazioni dei domini che lega: le
transizioni possono avvenire o da nodi del modulo m o da uscite di box (quindi
N m ∪Retns m ) e vanno verso o nodi interni del modulo o punti di chiamata di altre box
(quindi 2 N
m
∪Calls m
)
Useremo le lettere singole per definire l'unione dei vari insiemi: N= Um Nm ,
B=Um Bm , Callsm=Um Callsm , Retnsm=Um Retnsm indicheranno rispettivamente tutti i
nodi, tutte le box, tutte le chiamate e tutti i ritorni e così via.
Se considerassimo come vertici di m tutti i suoi nodi interni, insieme alle
chiamate ed ai ritorni, potremmo vedere ogni modulo come un grafo su un insieme di
vertici. Sia l'insieme dei vertici Vm=Nm ∪ Callsm ∪ Retnsm.
48
Figura 5: Un RSM formato da due moduli
Nella figura 5 è mostrato un esempio grafico di com'è strutturato un RSM.
Nell'esempio proposto la macchina ricorsiva S e costituita da due moduli, S1 e S2. Il
modulo di partenza è S1, quindi in S il valore min sarà uguale ad S1, L'unico nodo di
entrata a tale modulo, e quindi punto di partenza di tutti i possibili run, è il vertice e1,
che costituisce l'insieme En1, e mentre si potrà uscire da tale modulo solo tramite i nodi
di uscita x1 e x2, che formano l'insieme Ex1. La funzione di etichettamento Y1 di S1
collega il nome della box b2 a quello del modulo S2. Per il modulo S2 gli insiemi En2 e
Ex2 saranno definiti come segue En2={e2}, Ex2={x3, x4} e la funzione Y2 assocerà il
nome della box b1 al modulo A1.
Per ogni RSM così formato, si può costruire una struttura di Kripke globale
Ks=(Q,Init,R,lab) dove Q è l'insieme degli stati, e ogni elemento è della forma (γ,u) ∈
B* × V tali che
•
γ = ϵ e u∈V oppure
•
γ = b1, b2, b3, ...bk, con k≥1 e ∀ i ∈ [1, k-1], bi 1 ∈ BY b  e u ∈V Y  b 
i
k
L'insieme degli stati iniziali è formato da Init ={(ϵ,u)| u ∈ start}. La funzione di
etichettamento non fa altro che attribuire agli stati della struttura di Kripke l'insieme di
proposizioni associate al vertice e il “tipo” di vertice (ossia call/ret/int): formalmente
lab((γ,u))=(η(u), z), dove z=int se u è un nodo, z=call se u è una chiamata, z=ret se è
un ritorno.
49
La relazione di transizione globale δ: Q → 2Q invocata su uno stato s = (γ,u)
restituisce uno stato s' = (γ',u') se e solo se vale una delle seguenti regole:
•
Mossa interna: u ∈ (Nm ∪ Retnsm)\ Exm u'∈ δm(u) e γ'= γ
•
Chiamata a un modulo: u = (b,e) ∈ Callsm , u' = e γ'= γ.b
•
Ritorno da una modulo: u ∈ Retnsm, u'= (b,u) dovuto a γ= γ'.b
Non resta che definire un run su tale struttura: dato un alfabeto Σ = 2AP, dove AP
è un insieme finito di proposizioni atomiche, sia

=×{call,
ret, int } . Data una

parola =1 , 2 , 3 , ,...∈  un run di Ks su una parola α è una sequenza di stati π =
s0, s1, s2, ... dove lab(si)= αi tale che lo stato iniziale s0 appartiene all'insieme Init e
∀ i∈ℕ
allora si+1 ∈ δ(si) , ossia da ogni stato si passa al successore utilizzando una
transizione ammessa nel modello.
Il linguaggio riconosciuto per un RSM S sarà

L(S)= {∈  ∣ c'è un run di K s su }
3.4 Sintassi e semantica di CARET
Sia Σ = 2AP dove AP è l'insieme finito delle proposizioni atomiche e l'alfabeto

ret, int } . Con P ci si riferirà all'insieme AP ∪ {call, ret, int} e
aumentato =×{call,
il modello della logica CARET ovviamente saranno le parole possibili su  .
Propositional Linear Temporal Logic of Call and Returns (CARET) su AP è un
insieme di formule definite sulla base della seguente sintassi:
φ := p | ¬φ | φ˅φ | Xgφ | Xaφ | X-φ | φUgφ | φUaφ | φU-φ
dove p è una formula atomica appartenente all'insieme P. La definizione della sintassi è
fatta sulla grammatica ridotta: infatti, come per le logiche temporali lineari standard,
50
possiamo ricavare gli altri operatori sfruttando le varie equivalenze vigenti fra formule
e operatori. Dato che la leggibilità delle formule risulta facilitata dall'uso degli ulteriori
operatori forniti, ovviamente useremo Fbφ al posto di TrueUbφ, Gbφ
sarà
l'abbreviazione usata per ¬Fbφ¬φ e così via.
Gli apici presenti in testa agli operatori modificano il loro meccanismo di base,
specificandolo nel contesto delle chiamate ricorsive e caratterizzando le singole
operazioni in base alla visibilità che vogliamo avere:
•
Xg e Ug sono usati come gli usuali Next e Until nelle logiche temporali come
LTL (la lettera g come apice a volte viene omessa poiché la vista globale
coincide con l'interpretazione abituale degli operatori temporali. Questa
accezione vale per tutti gli operatori, anche quelli non specificati nella sintassi,
siano essi anche operatori derivanti dalla logica classica).
•
Xa e Ua vengono usati con la accezione di visibilità locale, dunque con Xaφ si
vuole imporre che il successore astratto dello stato corrente soddisfi φ, mentre
φ1Uaφ2 richiederà che nel cammino generato a partire dalla posizione corrente
che tiene traccia dei nodi visitati all'interno dello stesso modulo valga la
condizione φ1Uφ2.
•
U- e X- sono proprietà che devono essere soddisfatte a “ritroso”, ossia condizioni
stabilite sugli stati precedenti al punto attuale a partire dall'ultima chiamata non
ancora ritornata. X-φ richiede che il caller della posizione corrente soddisfi la
proprietà φ. φ1U-φ2 richiede che il path generato a partire dal caller fino al punto
attuale soddisfi φ1Uφ2
Data la spiegazione informale delle interpretazioni relative ai nuovi operatori,
non resta che puntualizzare la sintassi esplicitamente. Per una parola
∈  la
semantica è definita induttivamente definendo quando una coppia (α,n) soddisfa φ dove
n∈ℕ . Una parola α soddisfa φ se e solo se (α,0) la soddisfa. Per una parola α su
 e n∈ℕ la semantica di CARET stabilisce che:

– (α,n) ╞ p ↔ α0 = (Y, d) e p ∈ Y o p = d (dove p∈ P)
51
– (α,n) ╞ ¬ p ↔ NOT((α,n)╞ p)
– (α,n) ╞ φ1˅φ2 ↔ (α,n) ╞ φ1 oppure (α,n) ╞ φ2
– (α,n) ╞ Xgφ ↔ (α, succgα (n))╞ φ ossia (α,n+1) ╞ φ
– (α,n) ╞ Xaφ ↔ succaα(n) ≠ ⊥ e (α, succaα (n))╞ φ
– (α,n) ╞ X-φ ↔ succ-α(n) ≠ ⊥ e (α, succ-α (n))╞ φ
– (α,n)╞ φ1Ubφ2 ↔ per b∈ {g, a, - }, se c'è una sequenza di posizioni i0, i1,…, ik
dove i0=n, tale che (α,ik) ╞ φ2 e per ogni 0 ≤ j ≤ k-1, ij+1=succaα(ij) e (α,n) ╞
φ1 .
3.5 Esempi di proprietà verificabili con CARET
Pre e post condizioni: Tipiche dei formalismi di verifica classici, come la semantica
assiomatica di Hoare che, dato un comando C, stabilisce due asserzioni, P che deve
essere valida prima dell'esecuzione del comando e Q che deve essere vera dopo
l'esecuzione, tali proprietà sono ancora impiegate per stabilire la correttezza di
procedure di linguaggi operativi di tipo imperativo. Per questa tecnica la correttezza su
di una procedura A può essere di tipo parziale, quindi si chiede che la precondizione p
sia vera fino a quando la procedura è invocata e, nel caso che la procedura termini, la
postcondizione q deve essere soddisfatta sul ritorno, oppure può essere definita di tipo
totale, che impone che la procedura termini sempre. Se assumiamo che tutte le
chiamate alla procedura A siano caratterizzate dalla proposizione pA, le seguenti
formule garantiranno
correttezza totale: φtotal : G [(call ˄ p ˄ pA)→Xaq]
correttezza parziale: φpartial: G [(call ˄ p ˄ pA)→¬Xa¬q]
Boundedness: Il numero di chiamate che non sono state chiuse da un return fino ad un
determinata posizione di una parola corrisponde al numero di entry occupate nello stack
fino a quel momento. Possono essere stabilite diversi tipi di condizioni per verificare
l'impiego dello spazio dello stack. Potremmo voler verificare che ogni chiamata debba
52
ritornare, dunque che lo stack venga svuotato infinitamente spesso. Tale affermazione
può essere tradotta in una formula utilizzando la seguente specifica
φempty : G (call)→Xaret)
Una condizione più debole che può essere richiesta è che lo stack sia ripetutamente
limitato, ossia che esista un numero naturale n tale che infinitamente spesso lo stack
contenga al più n chiamate non ancora chiuse. Tale proprietà può essere espressa dalla
formula
φrep-bounded : FG (call)→Xaret)
Nonostante questa funzione non impedisca la possibilità che lo stack cresca
infinitamente e non potendo tra l'altro esprimere la proprietà che lo stack abbia sempre
una capienza limitata tramite gli strumenti forniti dalla logica, tuttavia la formula φrepbounded
garantisce che almeno in alcune posizioni esiste al più un numero n naturale di
chiamate in attesa.
Proprietà locali: L'aver introdotto la possibilità di esprimere gli operatori classici come
operatori con vista locale ci permette di verificare tutte quelle specifiche esprimibili
tramite LTL sui singoli moduli ignorando completamente tutte le computazioni che
avvengono durante le chiamate alle altre componenti e concentrandoci invece sul path
dei vertici appartenenti al singolo modulo A preso in analisi, generato a partire da un
nodo appartenente ad A e ottenuto applicando ripetutamente l'operatore Xa. Se con tA
rappresentiamo il controllo all'interno del modulo A, potremmo ad esempio facilmente
esprimere la proprietà di risposta “ogni richiesta p è seguita dalla risposta q” in maniera
locale impiegando la formula
φlocal: G [( p ˄ tA)→ Fa q]
Proprietà per il controllo dello stack: Come per il caso del path locale ottenuto da
ripetute applicazioni dell'operatore Xa, applicazioni ripetute dell'operatore X- ci
53
permettono di ottenere un path che elenchi tutte le chiamate presenti nello stack a
partire dalla chiamata più recente. Sull'ispezione dello stack si possono formulare una
serie di osservazioni interessanti da verificare per garantire proprietà di sicurezza. Ad
esempio si potrebbe voler imporre che l'invocazione di un determinato modulo A possa
avvenire solo all'interno di un modulo B e senza che venga mai invocata una chiamata
su un modulo C. La formula seguente esprime esattamente questa specifica.
φstack: G ( call ˄ pA→ (¬pC)U- pB]
In generale qualsiasi proprietà sullo stack che possa essere espressa come una parola di
un linguaggio regolare star-free (ossia come espressione regolare formata da simboli,
l'insieme vuoto, operatori booleani e concatenazioni, ad esclusione della stella di
Kleene), può essere anche tradotta in una formula di CARET: Tale affermazione risulta
essere particolarmente utile nel caso in cui cerchiamo di realizzare specifiche che
rilevino domini di sicurezza, delegazioni di autorità e politiche di gestione dello stack
implementate in numerosi linguaggi moderni come Java. Un'ultima annotazione deve
essere fatta per evidenziare un'altra capacità offerta dalla logica: permette di annidare
differenti tipi di modalità, come ad esempio accoppiare il concetto di caller con quello
del return locale per poter definire proprietà come “la proprietà è vera per tutto il
modulo attuale fino alla sua uscita”. Basta pensare che riusciamo a riferirci all'ingresso
del modulo in cui c'è il token attuale tramite X- e che il controllo su tutti gli stati
successivi non è altro che il path ricostruito tramite l'utilizzo del next locale Xa. La
formula corrispondente a tale proprietà è facilmente così ricostruibile
φupon-return: X- Xa φ
Sequenze di interrupt-driven: Software che si basano sul meccanismo di interruptdriven solitamente presentano la caratteristica di avere la profondità dello stack molto
limitata. L'occorrenza di ulteriori interrupt mentre si sta gestendo un'altra operazione
provoca l'aumento delle entrate presenti e il tentativo di stimare la grandezza massima
che può raggiungere lo stack è stata oggetto di numerosi studi. Ancora una volta le
54
potenzialità di CARET permettono di esplicitare condizioni utili per la verifica in tali
contesti: potremmo ad esempio specificare che “se mentre viene trattato un interrupt
generato da una componente e tale computazione non viene interrotta da altri segnali
provenienti dalla stessa componente, allora considera φ vera” impiegando la seguente
formula
φno-rec-int: G (( call ˄ pint) → ¬X- F- pint ) → φ
Codifica di RSM: La flessibilità e la potenza della logica permette di codificare i
possibili comportamenti di un RSM in modo analogo a come avviene la codifica di
macchine a stati finiti in LTL. Per un RSM S viene costruita una formula φS
introducendo una proposizione per ognuno degli stati di S e assicurando il rispetto delle
regole locali prodotte dall'evoluzione di S usando il successore globale. Bisogna però
tenere conto che una qualsiasi chiamata (b,e) deve essere associata a un ritorno del tipo
(b,x): per realizzare tale condizione dobbiamo stabilire che dovunque sia vera la
proposizione φ(b,e) deve essere anche vera ¬Xa true , oppure deve esserci un ritorno
(b,x) in cui siano vere sia Xap(b,x) che XgFpx. Solo in questo modo, qualunque sia la
macchina a stati ricorsiva S e la formula costruita su di esso φS, possiamo affermare che
se φS→φ allora S╞ φ
3.6 Verifica formale delle formule della logica CARET
La domanda di model checking classica che ci poniamo nel momento in cui
abbiamo a che fare con un modello S e una formula φ, è” S soddisfa φ?”. Ovviamente
nel contesto della nuova logica fin qui definita il problema del model checking si
propone su un RSM S e una formula in CARET φ. Alur, Etassami e Madhusudan
forniscono una prova formale della possibilità di effettuare model checking su queste
specifiche costruendo un recursive generalized Büchi automaton (intuitivamente è RSM
su cui sono state definiti condizioni generalizzate di Büchi) a partire dall'RSM S e la
negazione della formula φ. Tale automa accetterà esattamente tutte quelle parole del
55
linguaggio accettato dall'automa S che non soddisfano le specifiche espresse in φ.
La costruzione dell'automa trae spunto dalla verifica delle formule LTL sugli
RSM. Infatti se si fossero dovute gestire specifiche in tale logica, si sarebbe costruito
un automa pushdown i cui stati si sarebbero presentati come una coppia (u, A) dove u
era il nodo corrente nell'RSM e A era un “atomo” che rappresentava l'insieme delle
formule vere in u, gestendo lo stack per memorizzare i nomi delle varie box tramite
l'inserimento dei nomi dei modulo chiamati e l'eliminazione della entry nel momento in
cui il modulo corrispondente effettua un return.
Per atomo di φ intendiamo un sottoinsieme Y di Cl(φ), ossia la chiusura su φ, che
rispetti le seguenti regole:
•
Per ogni φ'∈Cl(φ), φ'∈Y se e solo se ¬φ'∉Y
•
Per ogni formula φ'˅φ''∈Cl(φ) , φ'˅φ''∈Y se e solo se o φ'∈Y o φ''∈Y
•
Per ogni formula φ'Ubφ''∈Cl(φ) , φ'Ubφ''∈Y se e solo se o φ''∈Y oppure φ'∈Y e
Xb(φ'Ubφ'')∈Y dove b∈{g, a, -}
•
Y contiene uno degli elementi dell'insieme {call, ret, int}
Alcune differenze sensibili fra LTL e C ARET, però, ci costringono a tenere traccia
di ulteriori informazioni e a trattarle opportunamente. Un esempio è quello riguardante
le chiamate: se prendiamo in considerazione una call, come accadeva per specifiche
LTL, ci aspettiamo che il nome della box sia inserito all'interno dello stack. Queste
informazioni, però, non sono sufficienti per controllare specifiche come quelle di
CARET. Dato che si possono dover verificare proprietà su path astratti, abbiamo bisogno
di conoscere tutte le proposizioni che erano vere nel nodo che ha effettuato la chiamata,
memorizzare in qualche modo tali proprietà per poi renderle nuovamente “attive” nel
momento in cui viene effettuata l'uscita dal modulo invocato. A tale scopo, dunque, non
solo nello stack verrà tenuta traccia del nome della box, ma verrà salvato anche
l'insieme delle formule che erano valide nel punto di call.
Altra differenza di cui bisogna tenere traccia deriva dalla creazione della
modalità caller, completamente assente in LTL. La possibilità di imporre, e dunque
dover verificare, specifiche sull'ultima chiamata in attesa ci obbliga a trovare una
metodologia per rappresentare correttamente tale condizione. Per le formule di tipo X56
φ', il trucco sta nel partire dalle specifiche soddisfatte dal nodo call che ha invocato il
modulo dov'è stata richiesta la specifica X- : se A è l'insieme delle proposizioni vere per
tale nodo call, allora X-φ' sarà vera se e solo se φ'∈A.
Generalmente, per le funzioni di tipo φ'Ubφ'', la garanzia che prima o poi φ''
diventi vera viene assicurata usando condizioni generalizzate di Büchi, una per ogni
formula di Until da verificare, con l'unica eccezione di quelle invocate con modalità
caller, poiché il path da verificare è finito (va da nodo call predecessore fino al punto in
cui è presente la proprietà φ'U-φ''). Tuttavia una funzione di Until con visibilità locale a
partire da un nodo u deve in qualche modo saltare i nodi che non appartengono al suo
stesso modulo. Per gestire tale situazione dovremmo aggiungere all'interno degli stati
informazioni che esplicitino se il nodo appartiene a una chiamata che finisce con un
return o meno. La condizione di Büchi che verifica tale condizione è costituita da i soli
stati che corrispondono alle chiamate senza ritorno.
3.7 Passare da RSM al RGBA
Dopo
le
considerazioni
effettuate
precedentemente,
formalizziamo
la
trasformazione. Per ogni RSM S=(M, start,{Sm}mєM) costruiremo un automa di Büchi
ricorsivo generalizzato (M', start',{S'm}mєM, F). M' = {m' | m∈M} è il nuovo insieme dei
nomi di moduli. Viene definito un insieme Tag={inf, fin} le cui etichette hanno lo scopo
di segnalare, in relazione al nodo u nel quale si rispetta l'insieme di formule
rappresentato dall'atomo A, se il run preso in considerazione è infinito, quindi non
uscirà mai, o se invece è finito, dunque alla fine compare un'uscita. Ogni nodo u∈Nm,
ad eccezione delle uscite, verrà trasformato in una tripla (u, A, t) in Sm', dove u è il
nodo, A l'atomo e t è una label di Tag. Stesso approccio viene adottato per quanto
riguarda le box, che in Sm' verrà trasformato nella tripla (b, A, t).
L'insieme dei nodi iniziali dell'automa sarà start'= {(u, A, t)| u∈start, φ∈A e A
non contiene nessuna formula del tipo X-φ', t = inf}
Per ogni vertice υ di S, diremo che un atomo è proposizionalmente consistente
con υ se η(υ)∩AP =A∩AP e la tipologia rispetta l'etichetta imposta nell'atomo (se è un
57
ritorno, ret∈ A, se il vertice è un nodo interno int∈ A, se è una chiamata call∈ A).
Con la notazione Atoms(φ) ci riferiremo invece a tutti i possibili atomi di φ.
Per ogni modulo m∈ M , riscriveremo la sua descrizione Sm = (Nm, Bm, Ym, Enm,
Exm, δm, ηm) come Sm' = (Nm', Bm', Ym', Enm', Exm', δm', ηm') dove
•
Nm' = {(u,A,t) | u∈Enm \Exm, A∈ Atoms(φ), t∈ Tag ed A è proposizionalmente
consistente con u } ∪ {(x,A,R) | x∈Exm, A,R∈Atoms(φ), t∈ Tag ed A è
proposizionalmente consistente con x }
•
Bm' = Bm × Atoms(φ) × Tag l'insieme dei nomi delle varie box
•
Ym' (b, A, t)= (Ym(b))' è la funzione di associazione dei nomi delle box con i
rispettivi moduli
•
Enm' ={(e,A,t) | u∈Exm, A∈ Atoms(φ), t ∈ Tag } sono i vertici di entrata
•
Exm' ={(x,A,R) | x∈Exm, A,R∈Atoms(φ), t∈Tag } sono i vertici di uscita
•
δm' : funzione di transizione
•
ηm' ((u, A, t)) = ηm(u) è l'etichettamento per i nodi interni, ηm'(((b,A,t),
(e,A',t')))=ηm((b,e)) è l'etichettamento per le chiamate mentre ηm'(((b,A,t),
(x,A',R)))=ηm((b,x)) per le uscite (in questo caso si aggiunge R che sono le
formule che erano vere nel modulo che aveva invocato quello da cui si è uscito.
In questo modo possiamo controllare i requisiti in modalità astratta)
La trasposizione della funzione di transizione nell'automa richiede una spiegazione
particolareggiata delle varie casistiche, dipendente dai tipi di nodi che si devono
relazionare:
Da nodo a nodo interno: δm' ((u, A, t)) conterrà (u', A', t') se e solo se:
•
u'∈ δm(u) e t = t'
•
GlNextReq(A, A') e AbsNextReq(A, A')
•
CallerFormulas(A)= CallerFormulas(A')
Da nodo a chiamata: δm' ((u, A, t)) conterrà ((b, A', t'),(e, A'', t'')) se e solo se:
58
•
(b,e) ∈ δm(u) e t = t'
•
A' è proposizionalmente consistente con (b, e)
•
GlNextReq(A,A') e AbsNextReq(A,A')
•
CallerFormulas(A)= CallerFormulas(A')
•
GlNextReq(A',A'') e CallerFormulas(A'')={X-φ'∈Cl(φ) | φ'∈A}
•
se t''=inf allora t=inf e non c'è nessuna formula Xaφ in A'
Da nodo a ritorno: δm' ((u, A, t)) conterrà (x, A', R), dove x∈Exm, se e solo se:
•
u∈ δm(u) e t = t'
•
GlNextReq(A,A'), AbsNextReq(A,A') e GlNextReq(A',R)
•
CallerFormulas(A)= CallerFormulas(A')
•
Non c'è alcuna formula X- φ in A'
Da ritorno a nodo: δm' ((b, A, t),(x, A', R)) conterrà (u, A'', t''):
•
u∈ δm((b,x)) e t'' = t
•
AbsNextReq(A,A') e CallerFormulas(A)= CallerFormulas(R)
•
R è proposizionalmente consistente con (b,e)
•
GlNextReq(R,A'') e AbsNextReq(R,A'')
•
CallerFormulas(R)= CallerFormulas(A'')
Le definizioni introdotte operano con i seguenti meccanismi
– AbsNextReq(A,A'): la relazione è vera se le specifiche con visibilità locale
richieste in A sono vere in A'
– GlNextReq(A,A'): la relazione è vera se le specifiche con visibilità globale
richieste in A sono vere in A'
– CallerFormulas(A): contiene tutte quelle formule in A che devono essere
rispettate sul vertice rappresentante l'ultima chiamata in attesa. Quindi
CallerFormulas(A) = {X- φ | X- φ∈ A}
59
Le formule di tipo Until, siano globali o locali, sono rimaste non trattate fino a
questo punto, ma, come accennato in precedenza, esse vengono garantite nella
definizione l'insieme F, ossia le condizioni dell'automa, che saranno costituite dai
seguenti insiemi:
•
un insieme contenente tutti i vertici (u, A, t) in cui t=inf
•
per ogni formula φ1Ugφ2 presente in Cl(φ), in F ci sarà un insieme formato da
tutti i vertici dove il corrispettivo atomo A soddisfa momentaneamente φ1Ugφ2
•
per ogni formula φ1Uaφ2 presente in Cl(φ), in F ci sarà un insieme formato da
tutti i vertici dove il corrispettivo atomo A soddisfa momentaneamente φ1Uaφ2 e
t=inf.
Una formula Until φ1Ubφ2 è soddisfatta momentaneamente da un atomo A se o φ2∈ A o
φ1Ubφ2 ∉ A ( con b uguale o ad a o g).
60
Capitolo 4
Giochi su grafi
4.1 Il contesto rappresentato dai giochi su grafi
Quando parliamo di sistemi dobbiamo considerare come ci viene fornito l'input,
chi è che effettua le scelte delle varie azioni e genera gli eventi. Nei capitoli precedenti
non abbiamo mai fatto riferimento a questa caratteristica e si potrebbe pensare che
all'interno di un sistema tali scelte vengano prese sempre dalla stessa entità. In realtà, se
consideriamo i sistemi non come componenti isolate, ma come facenti parte di un
ambiente con il quale possono interagire, ci rendiamo conto che non sempre gli eventi
sono determinati da meccanismi “interni”, ma vi sono degli stati le cui transizioni sono
provocate, e quindi in senso lato “controllate”, da tale ambiente esterno. A livello
logico, dunque, l'insieme di tutti gli stati di un modello può essere considerato come
partizionato in sottoinsiemi: ogni entità controlla un sottoinsieme di stati e in quegli
stati solo quell'entità può scegliere quale transizione effettuare. In questo scenario sorge
l'interesse di verificare proprietà peculiari, come, ad esempio, accertarsi che l'ambiente
non possa forzare il sistema in uno stato di errore, qualunque input possa esso generare.
L'approccio di verifica formale fra i sistemi detti “chiusi” (sistemi in cui l'input è
parte controllata dal sistema) e quelli “aperti”(sistemi in cui l'input è derivato dalla
interazione sincrona o asincrona con il rispettivo ambiente) presenta dunque sfide
differenti. Conoscendo interamente i meccanismi di un modello chiuso, la verifica
formale ha come obiettivo la validazione, ossia si concentra sull'accertarsi a posteriori
se le specifiche proprietà espresse siano o meno soddisfatte dal modello. Data, invece,
una descrizione di un sistema in cui alcuni dei simboli dell'input sono prodotti da una o
più fonti non controllabili, ci si può anche chiedere se il nostro sistema, attuando una
determinata politica di scelte, possa fare in modo che le specifiche desiderate siano
soddisfatte, indipendentemente dagli atteggiamenti e dalle transizioni operate dalle altre
entità. Tale problema viene definito in letteratura come controller synthesis problem ,
61
poichè quello che si vuole costruire è una sorta di controllore che riesca a reagire agli
input del sistema in maniera tale da conservarne la correttezza rispetto a determinate
proprietà-. Da questo concetto è derivata la problematica di realizzare delle strategie
vincenti in un gioco a due o più giocatori. La risoluzione dei problemi di sintesi è il
campo principale in cui la verifica formale tanto si è dedicata allo studio della teoria dei
giochi [39][9] [33].
4.2 Le caratteristiche di un gioco
Data la varietà di scenari reali simulabili tramite modelli di giochi, è conveniente
considerare alcune macro-caratteristiche necessarie a distinguere le varie tipologie di
giochi e comprendere le peculiarità salienti di essi.
Giocatori: Possono esservi giochi a singolo giocatore, a due o più giocatori fino ad
arrivare a giochi con un numero di infinito di essi
Mosse: I giocatori possono scegliere le loro mosse simultaneamente o sequenzialmente
Durata: Possono esservi giochi a una singola mossa fino ad arrivare a giochi di durata
infinita
Obiettivi: Può esserci un solo giocatore vincente o una suddivisione di giocatori
vincenti/perdenti più o meno equilibrata
Informazioni: I giocatori possono o non possono memorizzare una storia delle mosse
fatte, proprie e/o degli avversari
Possiamo rappresentare un gioco in diversi modi. La verifica formale si occupa
principalmente di giochi cosiddetti in forma estesa, che vengono effettuati sulla base di
grafi. I giochi che modellano sistemi hanno le principali caratteristiche della reattività,
ossia il programma compete contro l'ambiente avversario, e della durata infinita, che
modella controllori non terminanti, come driver o sistemi operativi.
Un gioco infinito è giocato su un grafo in cui insieme di vertici è partizionato in
tanti sottoinsiemi per quanti sono i partecipanti. I giocatori costruiscono un cammino
62
infinito muovendo il token attraverso il grafo. Dopo ω mosse viene determinato il
vincitore.
Una strategia per un Giocatore è una tabella che contiene la mossa da effettuare
per ogni gioco che arriva in un determinato vertice appartenente a quel Giocatore.
Le condizioni di vincita possono essere molteplici. Alcune delle più frequenti
sono la raggiungibilità (il token deve raggiungere un determinato stato facente parte di
un insieme di stati definito in precedenza), le condizioni di Büchi, quelle di Co-Buchi,
giochi di parità e giochi di Request-Response.
L'obiettivo non è solo trovare una strategia che sia vincente per un determinato
giocatore, ma anche cercare di minimizzare il più possibile il tempo di attesa, ossia il
numero di mosse per ottenere il soddisfacimento della condizione.
4.3 Definizione formale di giochi su grafi finiti
Ogni gioco è caratterizzato da un'arena e da delle condizioni di vincita. Ai fini di
questa tesi presenteremo formalmente solo i casi di giochi a due giocatori, il Giocatore0
e il Giocatore1. Sono effettuati su grafi diretti, il cui insieme di stati è partizionato in
due sottoinsieme di vertici che determinano le posizioni nelle quali la mossa appartiene
a all'uno o all'altro dei giocatori. A livello grafico i vertici appartenenti al Giocatore0
saranno disegnati come cerchi mentre i vertici appartenenti al Giocatore1 come
rettangoli.
4.3.1 Definizione di grafo di gioco
Un grafo di gioco (o arena) è una quadrupla :
A=(V, V0, V1, E)
che consiste in un grafo diretto (V, E) dove l'insieme dei vertici V è l'insieme delle
posizioni del Giocatore0 (V0) e del Giocatore1 (V1). Le mosse possibili sono date dalla
63
relazione E ⊆ V×V . L'insieme dei successori di un nodo v è definito come
vE ={v ' ∈V ∣ v , v ' ∈E }
4.3.2 Definizione di partita o run
Una partita di un gioco (o run di un gioco) può essere immaginata nel seguente
modo: all'inizio del gioco il token è posizionato su un vertice iniziale e ad ogni passo il
giocatore a cui appartiene il vertice dove risiede il token muove tale token in uno dei
vertici successivi a sua scelta. In questo modo i giocatori costruiscono una partita, ossia
una sequenza di vertici, che può essere infinita o finita, nel momento in cui venga
raggiunto un vertice senza successori. Un vertice v' è definito punto morto se vE=∅.
Dunque una partita in un'arena A è :
•
Una sequenza infinita ρ = v0,v1,v2,...∈ Vω con v i , v i1∈E ∀ i∈ (prende il
nome di partita infinita)
•
Una sequenza finita ρ = v0,v1,v2,... vn∈ V+ con v i , v i1∈E ∀ in e vnE=∅.
4.3.4 Definizione di gioco e di insieme vincente
Sia A un'arena e Win⊆V  . Un gioco G è una coppia
(A,Win)
dove A è l'arena del gioco e Win è l'insieme vincente. Una partita ρ è vinta dal
Giocatore0 nel gioco G se
•
•
∈Win , dove ρ è una partita infinita
se vn è un vertice del Giocatore 1 e vn E=∅, dove ρ è una sequenza finita ρ =
v0,v1,v2,... vn∈ V+
64
Il Giocatore1 vince in ogni altro caso.
L'insieme Win può essere espresso tramite qualsiasi condizione di vincita utilizzabile
sugli ω-automi (rimandiamo al capitolo 1 paragrafo 5). A seconda della condizione di
accettazione utilizzata parleremo di giochi di Büchi, giochi di Muller, giochi di parità,
etc.... Per uno studio più specifico delle varie condizioni di accettazione rimandiamo a
[29]
Per comprendere meglio le definizioni precedenti vediamo un esempio di gioco
utilizzando anche una rappresentazione grafica (Figura 6)
•
L'insieme dei vertici è V= {s,v1,v2,v3,v4,v5,v6,v7, v8}.
•
L'insieme V0 dei vertici il cui controllo appartiene al Giocatore0 sono
V0={s,v2,v4,v5,v7}
•
L'insieme V1 dei vertici il cui controllo appartiene al Giocatore0 sono
V1={s,v1,v3,v6, v8}
•
L'insieme delle transizioni sarà E={(s,v1),(s,v3),(v1,v2),(v2,s),(v2,v6),(v3,v4),
(v3,v5),(v4,v2),(v4,v6), (v4,v8), (v5,v5),(v6,v7),(v7,v7)}
•
Una partita valida sul gioco proposto è ρ=(s,v1,v2,)ω
•
Una partita finita vincente per il Giocatore0 è ρ=s,v3, v4, v8
Figura 6: Un grafo di un gioco a due giocatori
65
•
Una partita finita vincente per il Giocatore1 è ρ=s,v3, v4, v5
•
Se come condizione di vincita, ad esempio, decidiamo di accettare tutte le parole
che visitano infinitamente spesso s, la partita infinita ρ=(s,v 1,v2,)ω sarebbe una
partita vincente per il Giocatore0. Con la stessa condizione di vincita una partita
infinita vincente per il Giocatore1 sarebbe ρ=s, v1,v2,v6,v7ω
4.4 I giochi su RSM
4.4.1 Motivazioni
Sappiamo che tramite i giochi su grafi possiamo analizzare alcune delle
problematiche attinenti alle esecuzioni dei sistemi che agiscono in ambienti non
controllati. Nei paragrafi precedenti abbiamo considerato i giochi solo su automi, ma,
come abbiamo enunciato precedentemente, esistono anche sistemi che possono essere
rappresentati da macchine ricorsive.
Il passaggio da automi su macchine ricorsive a giochi a due giocatori su tali
macchine è abbastanza intuibile: come per qualsiasi gioco divideremo l'insieme dei
vertici in due sottoinsiemi e un giocatore può scegliere la prossima mossa se e solo se il
nodo corrente appartiene alla sua partizione. Di questi giochi sono state studiate
principalmente due proprietà specifiche: la raggiungibilità, ossia decidere se uno dei
due giocatori ha una strategia per forzare il sistema a entrare in uno degli stati
specificati, e la safety, in cui il giocatore deve forzare il sistema a stare in un specifico
insieme di stati definito in precedenza.
4.4.2 Giochi su grafi ricorsivi
Dato un alfabeto Σ, sia Σ* l'insieme delle parole finite e Σω l'insieme delle ω-
66
word su Σ. Per ogni n∈ℕ sia [n] l'insieme {1,...,n}
Un grafo di gioco ricorsivo A su Σ è una tripla
G=(M, min,{Am}mєM)
dove:
•
M è un insieme finito di nomi di moduli
•
min è modulo iniziale
•
Am è il modulo di gioco di ogni m∈M
Un modulo di gioco è una tupla
(Nm, Bm, Ym, Enm, Exm, P0m, P1m, δm, ηm)
dove:
•
Nm: un insieme finito di nodi
•
Bm: un insieme finito di box
•
Ym: è una funzione di etichettamento Ym: Bm→M che associa un modulo ad ogni
box
•
Enm: è un insieme non vuoto di nodi di entrata
•
Exm: è un insieme non vuoto di nodi di uscita
•
P0m e P1m: partizioni in cui è diviso l'insieme
N m ∪Bm che indica i nodi che
sono controllati dal giocatore0 e giocatore1
•
N
δm: funzione di transizione m : N m∪Retns m  2
•
ηm: funzione di etichettamento ηm: Nm→ Σ che associa una lettera in Σ ad ogni
∪Callsm
m
nodo
67
Figura 7: Un'arena di un gioco ricorsivo
Per comprendere in maniera più immediata il significato di ogni componente,
presentiamo nella Figura 7 l'RSM proposto nel capitolo 3 come grafo di un gioco
ricorsivo. Come per i grafi di giochi flat, abbiamo mantenuto la notazione che i nodi a
forma di cerchio appartengono al Giocatore0 e i nodi quadrati al Giocatore1.
Il grafo di gioco G rappresentato nella Figura 7 è costituito da due moduli, A1 e A2. Il
modulo di partenza è A1, quindi in G il valore min sarà uguale ad A1, L'unico nodo di
entrata a tale modulo, e quindi punto di partenza di tutti le possibili partite, è il vertice
e1, che costituisce l'insieme En1, e mentre si potrà uscire da tale modulo solo tramite i
nodi di uscita x1 e x2, che formano l'insieme Ex1. La funzione di etichettamento Y1 di A1
collega il nome della box b2 a quello del modulo A2. Per il modulo A2 En2={e2},
Ex2={x3, x4} e la funzione Y2 assocerà il nome della box b1 al modulo A1.
Un discorso particolare è necessario per specificare come opera la funzione di
transizione. Data la possibilità di effettuare chiamate ricorsive, le transizioni possono
essere effettuate da nodo a nodo o da nodo a box, quindi invocazioni ad altri moduli, o
da box a nodo, ossia ritorni da moduli che erano stati invocati precedentemente.
Dunque definiremo due nuovi insiemi:
Callsm={(b,e) | b∈B m , e ∈EnY
m
b 
}
dove ogni elemento (b,e) è una chiamata del modulo m al modulo m', che corrisponde
all'etichetta b
68
(quindi Ym(b)=m'), ed e è uno dei nodi di entrata di m' (ossia e ∈Enm ' ).
Retnsm={(b,x) | b∈ B m , x ∈ExY
m
 b
}
dove ogni elemento (b,x) è un ritorno del controllo da un modulo m', che corrisponde al
modulo associato all'etichetta b (quindi Ym(b)=m'), al modulo mm quando viene
chiamata l'uscita nel modulo m' dal nodo x di uscita (ossia x ∈Ex m ' ).
Specificati questi due nuovi insiemi risulta essere dunque più intuibile come
opera la funzione di transizione e quali sono le motivazioni dei domini che lega: le
transizioni possono avvenire o da nodi del modulo m o da uscite di box (quindi
N m ∪Retns m ) e vanno verso o nodi interni del modulo o punti di chiamata di altre box
(quindi 2 N
m
∪Calls m
)
Useremo le lettere singole per definire l'unione dei vari insiemi: N=Um Nm ,
B=Um Bm , Callsm=Um Callsm , Retnsm=Um Retnsm indicheranno rispettivamente tutti i
nodi, tutte le box, tutte le chiamate e tutti i ritorni e così via.
Se considerassimo come vertici di m tutti i suoi nodi interni, insieme alle
chiamate ed ai ritorni, potremmo vedere ogni modulo come un grafo su un insieme di
vertici. Dovremmo però mappare la divisione di tale insieme in partizioni dove le varie
componenti sono gestite da uno dei giocatori. Sia l'insieme dei vertici Vm=Nm ∪ Callsm
∪
Retnsm, L'insieme di vertici appartenenti a un giocatore λ sarà definito come
V m= N m∩P m ∪{b , x ∈Retns m∣b∈ P m } . I vertici di chiamata non appartengono a
nessun giocatore.
Verranno fatte delle restrizioni:
•
Per ogni modulo sarà presente un solo nodo di entrata. Questo nodo avrà come
notazione em. Questa restrizione non comporta nessuna perdita di generalità
poiché possiamo ridurre un grafo con entrate multiple come un grafo con tutte
moduli con una singola entrata, generando tante copie quanti sono i nodi in Enm,
mettendo in ognuna di esse un nodo di entrata diverso e gestendo
appropriatamente le chiamate e i ritorni.
•
Da qualsiasi nodo di Nm non posso andare al nodo di entrata di quel modulo e
69
per ogni punto di uscita non sono previste transizioni verso nodi di Em o verso
box (quindi una volta arrivati in un nodo di uscita, l'unica transizione possibile è
uscire dal modulo)
•
Da un'uscita non si può andare verso un punto di chiamata
4.4.3 Stato del gioco
Uno stato di un gioco è un elemento del tipo (γ,u) dove γ è una concatenazione
di elementi in B e rappresenta la successione di chiamate a moduli effettuate che non
hanno avuto ancora un return, ed u è il nodo dell'ultimo modulo in γ dove risiede
attualmente il controllo.
Formalmente quindi  , u ∈ B ✶× N in cui
•
γ = є e u ∈N m oppure
•
γ = b1, b2, …, bk,
in
b1 ∈B m e per ogni
¿
i∈[1, k −1] se Y(bi) = m allora
bi 1 e u∈ N m ' dove Y(bk) = m'
Uno stato del tipo (b1,b3,b4,u) ci direbbe dunque che dopo essere partiti dalla macchina
Amin è stata effettuata una chiamata alla box b1 associata alla macchina Am (ovviamente
Y(b1)=m) , da questa è partita una chiamata per la box b3 associata al modulo Am' , che a
sua volta ha effettuato una call verso la box b4 associata a Am''. Il vertice u sarà la
posizione corrente dove è presente il token e ovviamente tale nodo apparterrà a Am''
4.4.4 Run di un grafo di gioco ricorsivo
Un run effettuato su un grafo che rappresenta un gioco ricorsivo è una sequenza
finita o infinita di stati s0,s1,... tale che:
•
s0 rappresenta lo stato in cui sono rimasto nel modulo iniziale, quindi s0=(ε,e0) e
fotografa la situazione in cui il token è attualmente su un vertice e0 e non si è
effettuata alcun tipo di chiamata ad altri moduli
•
se sj=(γ,u),
u ∈N m allora sj+1=(γ',u') che è lo stato seguente nel run, deve
70
essere ricavato da sj rispettando una delle seguenti regole:
Mossa interna: u ∈N m ∖ Ex m , u ' ∈m u e γ'=γ, quindi il token è passato da un nodo
u in Nm a un altro nodo u' in Nm usando una transizione valida. Ovviamente le due
stringhe che tengono traccia dei moduli chiamati saranno uguali (poiché nessuna
chiamata o ritorno da altro modulo è stato effettuato)
Chiamata a un modulo: u ∈ N m ∖ Ex m , b , e m ' ∈m  u , u' = em' e γ' = γ.b. Dal nodo
u in Nm la transizione effettuata è stata un chiamata a una box b, quindi u' sarà un nodo
di entrata del modulo corrispondente alla box b e nella stringa dobbiamo concatenare a
γ il nome di tale box.
Ritorno da un modulo: u ∈Ex m , γ = γ'.b, u ' ∈m ' b , udove b∈B m ' . La situazione
mappata è quella in cui dal modulo Am associato alla box b presente nel modulo Am'
ritorno in quest'ultima macchina. La concatenazione di moduli deve tener traccia
dell'uscita dal modulo precedentemente invocato, dunque viene effettuato sulla stringa
γ il pop dell'elemento b dato che l'operazione è conclusa e u' è lo stato in cui mi porta
l'uscita dal modulo Am
Una partita in A è uno dei possibili run in A. Distingueremo gli insiemi delle
possibili partite in due sottoinsiemi:
•
Πf è l'insieme di tutte le partite con un numero di mosse finito
•
Πω è l'insieme di partite con un numero infinito di mosse
Dato lo stato s = (γ,u) definiamo V(s) una funzione che restituisce il vertice
corrispondente a quello stato. Le situazioni in cui possiamo trovarci sono due
•
Se γ=ε oppure è u un nodo interno o un nodo di entrata
u ∈ N m ∖ Ex m
allora
V(s)=u.
•
Altrimenti V(s)= (b,u) dove b è ottenuto da γ= γ.b
4.4.5 Funzione CTR
La funzione ctr ci permette di identificare in quale modulo è presente il token
dopo un qualsiasi run finito. Tale funzione è definita formalmente nel seguente modo
71
ctr : Πf → M
e per ogni
. s ∈ f , ctr(π.s) = m dove
V  s ∈V m , dunque se l'ultimo elemento
del run è un vertice che appartiene all'insieme del vertici del modulo Vm, restituisce il
nome di tale modulo.
4.4.6 Funzioni s-history, storia (Hts) e storia locale (μ)
Per ogni partita finita dove ctr(π)=m, la funzione history ha lo scopo di dirci
tutto quello che è accaduto durante la partita fino alla chiamata attuale al modulo m.
Un s-history su uno stato s è un elenco di βi, dove i vari βi non sono altro che
una sequenza di vertici che appartengono alla stessa box. Dato che lo scopo è tenere
traccia dei nodi visitati durante tutto la partita fino a quel momento, all'inizio si parte da
una stringa che contenga solo il primo nodo di partenza. Quando il token passa sugli
altri nodi, di volta in volta si concatenano gli identificativi di tali vertici alla stringa di
partenza ottenendo una serie ordinata che rispecchia la sequenza di mosse effettuate.
Ogni volta che viene chiamata una box, viene creata una nuova stringa dove allo stesso
modo vengono concatenati i nodi visitati nel modulo associato alla box e così via.
Formalmente, per uno stato s=(b1...br, u) la corrispondente s-history è una sequenza
ordinata 〈1 , 2 ,... , r 〉 dove per ogni i∈[r ] vale che i ∈V ✶m dove bi ∈B m .
Come Hts, la funzione “storia”, invece, definiamo una funzione che associa ad
ogni partita con mosse finite una s-history sulla base delle seguenti regole:
Se sono nello stato iniziale del run
=s0 = , e m  allora la funzione Hts restituisce
in
una sola stringa in cui è presente solo il nome del nodo di partenza Hst =〈 e m 〉
in
Se sono state fatte altre mosse, allora π = π'.(γ',u') dove π' = π''. (γ,u). Consideriamo
allora Hts(π')= 〈1 , 2 ,... , r 〉
- Se viene effettuata una mossa interna, quindi se γ'= γ, allora Hst(π) =
〈 1 , ... ,  r−1 , r . u 〉
72
- Se viene effettuata una call, quindi
γ'= γ.b con Y(b) = m', allora Hst( π)
=
〈 1 , ... ,  r . u , e m ' 〉
- Se viene effettuato un return, quindi γ= γ'.b, allora Hst( π) = 〈 1 , ... ,  r−1 〉
Dunque se viene effettuata la mossa interna, ci limitiamo a concatenare il nome del nuovo
interno nell'ultima stringa della lista, se invece viene effettuata una call, dobbiamo creare una
nuova stringa per tenere traccia delle mosse avvenute in quel modulo, mentre tutte le altre
stringhe non vengono modificate, mentre se avviene una azione di return le informazioni su
cosa è successo in quel modulo non interessano più e vengono cancellate.
Resta da definire la storia locale μ. In maniera informale, la storia locale non è
altro che quella parte di una partita a mosse finite che si è svolta nel modulo dove
attualmente risiede il controllo, dunque l'ultima stringa contenuta nella Hts. La
definizione formale conseguente a questa osservazione risulta essere che, dopo un
gioco con un numero di mosse finite π e avendo calcolato Hts(π) = 〈1 , ... ,  k 〉 , allora
μ(π)=βk
4.4.7 Condizioni di vincita
Per chiudere la parte relativa alla descrizione formale delle caratteristiche dei
giochi su macchine ricorsive, ci resta solo da esplicitare gli obiettivi che vogliamo
cercare di raggiungere, ossia le condizioni di vincita che sarà necessario rispettare nella
strutturazione di una strategia vincente per il Giocatore0.
Un insieme vincente su Σ è un linguaggio ω-regolare su Σ, ossia un linguaggio
regolare
L⊆  . Un gioco ricorsivo è una coppia (A, L) dove A è un game graph
ricorsivo e L è l'insieme vincente, entrambi costruiti sullo stesso alfabeto Σ.
Una partita è vincente se ∈L , dove la funzione di etichettamento η su run
restituisce la stringa generata dalla concatenazione dei singoli simboli ottenuti dalla
funzione di etichettamento applicata su ogni vertice di quel path.
Ovviamente l'obiettivo che si cerca di perseguire è quello di, dato un gioco
ricorsivo (A,L), verificare se esista (ed eventualmente anche estrapolare) una strategia
vincente per il Giocatore0.
73
Capitolo 5
Le strategie modulari
5.1 Motivazioni
La complessità dei giochi pushdown e su macchine ricorsive è già stata studiata
e esistono algoritmi che risolvono tali giochi nel caso in cui ogni giocatore conosca la
storia globale[15][14]. Tuttavia ci si è chiesto se si potessero sviluppare algoritmi per
giochi su macchine a stati ricorsive tramite strategie modulari, ossia strategie che si
basano esclusivamente sulla memoria locale di ogni modulo. La scelta della mossa da
fare, dunque, viene eseguita esclusivamente sulla base della storia locale,
indipendentemente dal contesto nel quale il modulo è invocato.
L'idea che ha motivato lo studio e l'analisi di questo tipo di strategie risiede nella
considerazione che se abbiamo un software il cui funzionamento è gestito in
componenti assimilabili a moduli, è più naturale pensare all'esistenza e la progettazione
di un controllore specifico per ogni modulo che si occupi di garantire il corretto
funzionamento della singola componente in qualsiasi contesto possa essa essere
invocata. Lo studio [3] che successivamente analizzeremo nel dettaglio dimostrerà che
possiamo decidere l'esistenza di una strategia modulare vincente per giochi con
condizioni esterne di tipo safety, Büchi e Co-Büchi deterministici o universali e con
specifiche LTL tramite la realizzazione di speciali automi che riconoscano alberi di
strategie vincenti per il Giocatore0.
5.2 Definizione formale di strategia modulare
In accordo con le entità e funzioni definite nel capitolo precedente, una strategia
modulare viene espressa per mezzo di un insieme di funzioni, una per ogni modulo, che
hanno lo scopo di esplicitare il comportamento del Giocatore0 durante tutta la durata
74
del gioco. Alla luce dell'introduzione fatta precedentemente, dovrebbe essere chiaro che
tali decisioni verranno prese non sulla base di informazioni globali, ma la conoscenza
del giocatore verrà limitata alla storia locale del modulo in cui in quel momento è
presente il token.
L'insieme di funzioni relative alla strategia modulare viene definito come
f ={ f m }m∈ M
dove
per
ogni
∀ m , f m :V ✶m ,V 0m V m tale
che
f m ∈  per
ogni
∈V ✶m , ∈V 0m .
La funzione prende in input una stringa costituita da un elenco di vertici, che
rappresentano i vari nodi per cui si è passati durante il gioco, e il cui ultimo vertice sia
un vertice controllato dal Giocatore0 e restituisce quella che è la prossima mossa, ossia
un vertice υ in cui andare, nel rispetto ovviamente delle transizioni possibili dal nodo di
origine.
Una partita conforme a una strategia modulare f è un run s0s1s2... tale che in ogni
vertice che appartiene al Giocatore0 è stata seguita la mossa esatta prevista da quella
strategia. Dunque per ogni i < |π| se ctr(s0...si) = m e V  s i ∈V 0m , allora il prossimo
stato in cui ci verremo a trovare sarà si+1=(γ', u') dove fm(μ(s0...si)) può essere o una
mossa verso un nodo interno u' o una chiamata a un modulo (b, u') per una qualsiasi
box appartenente al modulo corrente.
5.3 Risolvere giochi con strategie modulari
Il problema che ci poniamo quando consideriamo un gioco su una macchina
ricorsiva che utilizzi strategie modulari è il seguente: dato un gioco ricorsivo (A,L)
esiste una strategia modulare vincente per il Giocatore0? In [3] è presentata una tecnica
per risolvere tale problematica usando un approccio di tipo teorico basato sulla
costruzione di particolari automi detti automi di strategia. Tuttavia prima di entrare nel
75
vivo della presentazione di tale tecnica abbiamo bisogno di introdurre alcuni concetti
ancora non trattati finora in questo elaborato.
5.3.1 Gli automi alternanti
Nei capitoli precedenti, riferendoci agli automi, abbia parlato di una
caratteristica fondamentale che in cui vengono distinti: automi deterministici, per cui la
funzione di transizione è univocamente definita e l'automa ha un solo run su ogni parola
in input, e automi non-deterministici, per i quali uno stesso simbolo può innescare
transizioni su uno o più stati e che, dunque, possiedono per ogni parola più di un run.
Dato che ogni parola può generare uno o più run, la classe degli automi nondeterministici a sua volta viene divisa in altre due sottocategorie: gli automi universali,
ossia che accettano una parola di input se e solo se tutti i run dell'automa sono
accettanti, e gli automi esistenziali (a volte detti semplicemente non-deterministici) che
accettano una parola di input se e solo se l'automa ha almeno un run accettante su tale
parola.
Tuttavia una terza sottocategoria di automi può essere generata a partire dalla
combinazione di automi universali e automi esistenzia, ossia macchine a stati finiti la
cui caratteristica fondamentale è quella di avere sia il branching universale che quello
esistenziale. Tali automi prendono la definizione di automi alternanti e vengono
rappresentati tramite una quintupla
A=(Σ, Q, q0, δ, F)
dove:
•
Σ è un insieme di simboli, ossia l'alfabeto
•
Q è l'insieme degli stati
•
q0 è lo stato iniziale
•
δ è la funzione di transizione, definita come δ: Q × Σ → B+(Q)
76
•
F è l'insieme degli stati finali
Per B+(Q) viene inteso l'insieme di formule booleane costruite con gli elementi
di Q tramite congiunzioni e disgiunzioni (le negazioni non sono permesse). Quindi la
funzione di transizione mappa una coppia di tipo (stato,simbolo) in una formula
booleana positiva costruita su Q.
La condizione di accettazione di un'automa alternante dipende da come viene
definito l'insieme degli stati finali e può essere una delle tante condizioni che sono
esprimibili su automi.
Per ogni S tale che
S⊆Q e sia φ una formula appartenente a B+(Q), diremo
che S soddisfa φ se l'assegnamento di verità che assegna true agli elementi di S soddisfa
φ.
Le transizioni di automi non-deterministici e universali possono essere formulate
in funzione di B+(Q): ad esempio passare una transizione del tipo δ(q,σ)=(q1, q2, q3) di
un automa non-deterministico esistenziale può essere scritta come δ(q,σ)=(q1 ˅ q2 ˅
q3), mentre se fosse universale basterebbe scriverla come δ(q,σ)=(q1 ˄ q2 ˄ q3).
Il fatto che negli automi alternanti posso avere sia congiunzioni che disgiunzioni
li rende estremamente potenti, poiché la scelta dell'insieme di stati in cui l'automa
decide di andare introduce contemporaneamente non-determinismo esistenziale e
quello universale. Se un automa alternante prevedesse la regola δ(q,σ)=(q1 ˄ q2) ˅ ( q3
˄ q4) allora l'automa accetta il suffisso della parola che inizia con σ a partire dallo stato
q se tale suffisso è accettato partendo da q1 e q2 oppure da q3 e q4.
Dunque da un automa non-deterministico o universale possiamo sempre
costruire un automa alternante che riconosca lo stesso linguaggio. Non è sempre
possibile invece, trasformare un automa alternante in un automa esistenziale od
universale, ma possiamo sempre trasformare un alternante di Büchi in un nondeterministico di Büchi e trasformare un alternante di co- Büchi in un universale di
co-Büchi. Vedremo successivamente che questa possibilità sarà utile per stabilire le
tipologie di problemi risolvibili con l'impiego di automi che riconoscano strategie
vincenti e inoltre ci aiuterà a comprendere la complessità di tale metodo.
77
Figura 8: Esempio di labelled tree
5.3.2 I Δ-labelled k-tree
Sia k un numero naturale e Δ un alfabeto finito.
Definiremo un Δ-labelled k-tree come una coppia (Tk, ν) dove Tk = (Z, E) è un
albero, costituito da un insieme Z di vertici con Z = [k*] e un insieme di archi E = {(y,
y.d) | y ∈[k ✶ ]e d ∈[k ] }. ν è una funzione di etichettamento ν: Z → Δ che assegna ad
ogni vertice di Z una lettera dell'alfabeto Δ. Con la lettera ϵ denoteremo la radice
dell'albero Tk e per ogni
y ∈Z il vertice dell'albero y.d sarà il suo d-esimo figlio.
Un esempio di Δ-labelled k-tree con Δ={a,b} e k=2 è presentato nella Figura 8
5.3.3 I two-way alternating parity tree automaton
I two-way alternating parity tree automaton sono particolari tipi di automi
alternanti costruiti su Δ-labelled k-tree. A differenza degli automi alternanti, dato che
“ereditano” l'insieme dei simboli dal k-tree, li esprimeremo con una quadrupla
A = (Q, q0, δ, W)
78
dove:
•
Q è l'insieme degli stati
•
q0 è lo stato iniziale
•
δ è la funzione di transizione, definita come δ: Q × Δ → B+({-1,0,1,...k}× Q)
•
W è la condizione di parità su Q
La differenza più evidente è che oltre a restituirci la formula booleana, ci
restituisce un elemento dell'insieme {-1,0,1,...k}: tale valore ha lo scopo di codificare la
direzione presa nel k-tree e lo stato dove bisogna transire a partire dal vertice in input
alla funzione: i valori {1,...k} servono per identificare i possibili k figli del nodo, -1 il
predecessore attuale e 0 il nodo stesso.
Introducendo questo nuovo meccanismo, possiamo ridefinire la definizione di
concatenazione per sfruttare il codice inserito nell'automa: dati
x ∈[ k ]✶ e i∈[k ] , allora
x.0 = x (concatenare zero ad una stringa lascia tale stringa invariata), ed (xi.(-1)) = x,
(quando la parola è concatenata con un -1 viene effettuato il pop dell'ultimo simbolo).
Un run di A su un Δ-labelled k-tree (Tk, ν), è un albero etichettato Tρ= (Rρ, Eρ) i
cui vertici appartenenti a Rρ sono etichettati dalla da una coppia (x,q), con
x ∈Z e q∈Q ossia stato del k-tree e stato dell'automa. Le regole con cui tali etichette
sono assegnate sono le seguenti:
•
La radice deve essere (ϵ, q0)
•
Se c'è un vertice y con etichetta (x,q), allora deve esserci un insieme F con
F ⊆{−1, 0,1, ... , k }×Q che soddisfa δ(q, ν(x)). Per ogni i , q ' ∈F , allora y ha
un figlio etichettato come (x.i,q')
Un run viene definito accettante se per ogni cammini infinito, prendendo dalla
label solo l'informazione relativa agli stati, la sequenza degli stati così ottenuta soddisfa
la condizione di vincita di A.
Un two-way alternating parity tree automaton può sempre essere trasformato in
un one-way non-deterministic parity tree automaton con un numero di stati
79
esponenziale rispetto al numero stati del automa di origine e con un numero di colori
lineare rispetto al numero previsto nella condizione di vincita dell'automa d'origine. Il
problema del vuoto per l'automa ottenuto può essere risolto in tempo polinomiale nel
numero degli stati e esponenziale nel numero dei colori.
5.3.4 Alberi di strategia
Fissiamo come dati di partenza :
•
insieme ordinato di h moduli M={m1, … , mh}
•
un alfabeto finito Σ
•
un grafo di gioco ricorsivo A=(M, min,{Am}mєM)
•
la definizione degli h moduli nella forma (Nm, Bm, Ym, Enm, Exn, δm)
•
un insieme di etichette  A={root , dummy}∪V ×{⊥ , ⊤}
Non ci resta che definire e spiegare in che cosa consiste un albero di strategia,
costrutto che ha lo scopo di codificare sotto forma sempre di albero i meccanismi delle
strategie modulari.
Un albero di strategia è un ΔA-labelled k-tree in cui:
•
Il simbolo root è un etichetta che verrà usata solo per identificare la radice
dell'albero
•
I sottoalberi che partono dai nodi figli della radice mapperanno la strategia per
ogni modulo. Dato che l'insieme dei moduli è ordinato, per ogni 0 < i ≤ h dal
sottoalbero che si diparte dall'i-esimo figlio corrisponderà la strategia relativa al
modulo mi
•
La radice di tutti i sottoalberi descritti nel punto precedente ha come etichetta il
nodo di entrata del modulo, ossia enm, con l'aggiunta del simbolo ⊤.
•
Per ogni nodo υ del k-tree appartenente alla rappresentazione di un modulo m ed
80
etichettato con la coppia (p,⊤):
– Se p è un return oppure un nodo interno (ad eccezione dei nodi di uscita),
allora tutti i nodi figli di υ prenderanno come etichetta i valori dei
successori di p insieme con la notazione ⊤/⊥. Inoltre se il nodo p
apparteneva al Giocatore0, allora si sceglie un nodo fra i successori di p e
quel nodo prenderà il valore ⊤ mentre tutti gli altri prenderanno ⊥. Se
invece il nodo p apparteneva al Giocatore1 non sappiamo con certezza
quale sarà il nodo scelto, dunque dobbiamo taggare tutti i possibili
successori con il valore ⊤.
– Se p è una chiamata, non codificheremo la chiamata ad altri moduli, ma
metteremo dei successori che corrispondano ai possibili ritorni dal nodo
invocato, con associati una qualsiasi notazione ⊤/⊥. In questo modo si
codifica il presupposto che la chiamata ad un altro modulo non può finire
in un return taggato con ⊥, qualunque siano le scelte del Giocatore1.
Un sottoalbero il cui nodo di radice venga taggato con il simbolo ⊥ non verrà
sviluppato, poiché le scelte precedenti dettate dalla strategia fino a quel momento
attuata forzano il sistema a non raggiungere quella parte di grafo, e quindi non vi è
alcuna necessità di stabilire una strategia per tale caso.
Altri sottoalberi che non hanno necessità di essere sviluppati sono quelli i cui
nodi radice sono del tipo (p,ζ) dove p∈Exm: tale etichettamento indica un nodo di
uscita, quindi dopo quel nodo si passerà in un altro modulo per il quale sarà espressa
una strategia modulare in uno degli altri sottoalberi (ricordiamo che abbiamo posto la
condizione che da un nodo di uscita non si possa ritornare in alcun nodo interno del
modulo né effettuare chiamate verso altri moduli).
Un frammento di un possibile albero di strategia per il gioco modulare proposto
nel Capitolo 2 è presentato nella Figura 9 (pagina seguente)
81
Figura 9: Un albero di strategia oer un gioco modulare
Come si può vedere, dato che nel sistema erano presenti due moduli, nell'albero di
strategia per quel gioco sono presenti due sottoalberi, le cui radici sono i nodi di
partenza dei due moduli taggati con il simbolo ⊤. I nodi in questione sono quelli
determinati dalle coppie (e1,⊤) ed (e2,⊤). Ricordando il grafo di gioco relativo al
modulo A1, sappiamo che dal vertice e1, appartenente al Giocatore0, era possibile
effettuare due transizioni: o andare verso il vertice n1 o chiamare la box b2. Dunque al
nodo (e1,⊤) vengono aggiunti due nodi figli corrispondenti alle transizioni possibili.
Essendo il vertice e1 appartenente al Giocatore0, l'algoritmo ci dice dunque di scegliere
una delle possibile transizioni che verrà effettuata. Nel nostro caso si è scelto di
invocare la box piuttosto che spostare il token nel nodo appartenente a n1. Quindi
metteremo il simbolo ⊤ come etichetta del vertice corrispondente alla call che sarà il
nodo ((b2,e2),⊤): da notare che, in riferimento alle chiamate, il primo elemento della
coppia che individua il vertice si presenta sempre come (nome_della_box,
nodo_di_entrata_corrispondente) e la stessa costruzione si può notare anche per i
vertici che mappano i ritorni dalle chiamate con l'ovvia differenza che comparirà il
nome del nodo di uscita corrispondente alla box. Il nodo che viene associato al vertice
n1 verrà labellato con il simbolo ⊥, che indicherà che quella strada può essere evitata e
dunque possiamo non sviluppare l'albero al di sotto di quel nodo. Quando si deve
82
sviluppare il nodo ((b2,e2),⊤), che è una chiamata ad un modulo, non si considereranno
i vertici e gli archi del corrispettivo modulo, ma solo le uscite. Quindi si collegheranno
ad esso tanti nodi quante sono le uscite e si assumerà che il modulo A2 esca solamente
dal nodo x4 e non dal nodo x3.
5.3.5 Automi che accettano alberi di strategia vincenti con condizioni di
safety
Nel paragrafo precedente abbiamo visto che è possibile costruire un automa che
accetti un Δ-labelled k-tree solo se esso rappresenta una strategia. Chiameremo questi
automi Astr. Il prossimo passo è cercare di modellare in qualche modo le condizioni di
vincita per realizzare un automa accetti solo alberi di strategia vincenti. Chiameremo
questo tipo di automa Awin.
Possiamo adesso costruire l'automa Awin che data una specifica safety in forma di
automa B=(Q, q0, δB) accetta un albero di strategia se e solo se questo rappresenta una
strategia vincente per quella specifica.
L'automa Awin è un two-way alternating tree automaton che deve svolgere tre
principali funzioni:
•
Deve simulare il comportamento di B, ossia il comportamento dell'automa che
rappresenta le condizioni di vincita, in maniera tale da garantire che le specifiche
per quel determinato gioco siano soddisfatte. Principalmente simula la funzione
di transizione di B. Inoltre quando ci si trova in un punto di uscita, l'automa deve
verificare che siano rispettate le condizioni delle avoid component, coppie
definite come (x,q) che individuano uscite dove non può finire il gioco con uno
stato q. L'utilizzo di tale struttura si dimostrerà fondamentale nei task successivi.
•
Deve garantire che le partite siano generate a partire dal nodo di entrata del
modulo iniziale e che non si possa uscire da tale modulo iniziale. Questa
condizione è posta principalmente con la motivazione che quelli che noi stiamo
verificando sono condizioni di vincita in cui le partite sono costituite da un
83
numero infinito di mosse: se potessimo uscire dal modulo iniziale il gioco
terminerebbe con un numero finito di mosse e sarebbe valutato perdente per il
Giocatore0 in ogni caso. A tale scopo vengono usate nuovamente avoid
component per indicare che non si può uscire da un nodo di uscita del modulo
min con qualsiasi stato.
•
Deve accertarsi che quando il ritorno da un sottoalbero è etichettato con il
simbolo ⊥, la chiamata da un altro modulo non finirà sicuramente in tale
ritorno, indipendentemente da come si comporta il giocatore1. Questo perchè
abbiamo detto che parti delle computazioni possono essere evitate e per quelle
non è previsto lo sviluppo di strategie. Se tale ritorno fosse nel modulo m
all'uscita x, verificare tale condizione corrisponde a mandare una copia
dell'automa alla radice del sottoalbero corrispondente a m per ogni avoid
component della forma (x,q)
Grazie a questa costruzione, la condizione di vincita di Awin che imporremo sarà una
condizione di parità in cui ogni stato viene mappato con il colore 0. In maniera molto
banale quello che andremo a verificare sarà solamente l'esistenza di un run sull'albero
costruito.
L'automa descritto finora dunque accetta un albero di strategia se e solo se
corrisponde a una strategia vincente per il Giocatore0. Essendo Awin un two-way
alternating tree automaton possiamo convertirlo in un one-way tree automaton nondeterministico e facendone l'intersezione con l'automa Astr (sempre convertito)
otteniamo un nuovo automa A' che accetta un albero se e solo se corrisponde a un
albero di strategia vincente. Questo rende possibile dunque la risoluzione del problema
dell'esistenza o meno di una strategia modulare vincente per un gioco con condizione di
vincita esterne di tipo safety.
5.3.6 Passare da condizioni di safety ad altre specifiche ω-regolari
Prenderemo in considerazione adesso condizioni espresse tramite l'impiego di
84
altri metodi di specifica . Il passaggio da condizioni safety a condizioni di specifiche ωregolari non può avvenire senza apportare delle opportune integrazioni al meccanismo
già presentato, ma vedremo che una volta risolte le problematiche introdotte per automi
deterministici di Büchi e co- Büchi, la soddisfazione delle specifiche tramite altre
tecniche baserà su tale soluzione il suo corretto funzionamento.
Condizioni espresse con automi deterministici di B üchi e Co-Büchi: Data una specifica
espressa tramite l'automa di Büchi B=(Q, q0, δB, W) e un grafo di gioco ricorsivo,
dobbiamo fare in modo da costruire un nuovo automa A'win che verifichi l'esistenza di
strategie modulari vincenti. Per fare questo dobbiamo considerare però un problema
che non avevamo con i giochi di tipo safety: quando un automa legge una chiamata in
un sottoalbero di un modulo m l'insieme di stati, il metodo proposto considera solo
l'insieme degli stati dove il gioco può essere quando esce da un modulo chiamato. Se
vengono impiegate invece specifiche di Büchi bisogna sapere come è stata svolta la
chiamata e identificare tutte le possibile sottopartite che raggiungono le uscite
individuate. Solo così è possibile verificare se vi sono stati presenti in W o no. Per far
fronte a questa problematica, possiamo fare in modo che l'automa mandi una copia di
un modulo chiamato per controllare tali sottopartite, e fare in modo da segnalare, se
trovati, gli stati finali per Büchi prima di continuare la partita nel modulo corrente. Il
problema è esattamente duale nel caso di automi di Co-Büchi.
Condizione espresse con automi universali di Büchi e Co-Büchi: Possiamo anche
decidere giochi ricorsivi utilizzando automi universali di Büchi, così come qualsiasi
specifica di tipo ω-regolare. Se consideriamo un linguaggio L che sia un linguaggio ωregolare , sappiamo che anche il suo complemento
L è un linguaggio ω-regolare e
che può essere accettato da un automa di Büchi non-deterministico. Tale automa, che
chiameremo B', lasciando inalterati stati e transizioni ma interpretando le specifiche
come universali e di Co-Büchi, in effetti non è altro che un automa universale di CoBüchi B, che semplicemente accetta una parola se e solo se B' non la accettava. Dato
che l'automa B' è stato costruito a partire dal complemento di L, il nuovo automa B
accetterà proprio il linguaggio L ed, essendo un automa universale con condizioni alla
85
co-Büchi, può essere impiegato nel metodo di risoluzione dei giochi che abbiamo visto.
Condizioni espresse tramite specifiche LTL: Un discorso analogo può essere effettuato
anche con la specifiche effettuate tramite la logica LTL su un insieme di proposizioni P
e avendo i nodi del grafo etichettati con simboli appartenenti a 2P. Anche per le formule
LTL, come abbiamo detto nel capitolo 1, possiamo costruire un automa di Büchi nondeterministico B¬φ su che riconosca le parole che soddisfano la formula negata,
dunque . Anche questo automa può essere interpretato, come per gli automi generati da
linguaggi ω-regolari, come automi universali di co-Büchi che accettano parole che
soddisfano la formula φ. Questo significa dire che possiamo decidere giochi su grafi
ricorsivi con specifiche di tipo LTL.
5.3.7 Complessità
La taglia di un automa di tipo Awin è O(|Q|2•|Ex|) dove Q è l'insieme degli stati
nell'automa relativo alle condizioni di vincita e Ex è l'insieme che contiene tutte le varie
uscite del grafo di gioco ricorsivo. Effettuare l'intersezione di questi due insiemi,
generare il nuovo automa non-deterministico A' che risolve il problema dell'esistenza di
strategie modulari per giochi ricorsivi e verificarlo con condizioni di tipo safety
richiede tempo O (|A|•exp(|Ex|•|B|)), dunque il problema è EXPTIME-complete.
Dato che le modifiche apportate per risolvere i giochi con condizioni espresse da
automi deterministici di
Büchi e Co-Büchi non apporta modifiche sostanziali al
funzionamento del processo di decisione e alle grandezze dei vari alberi, anche decidere
l'esistenza di strategie modulari per giochi con tali condizioni ricade nei problemi
EXPTIME-Complete, come del resto vale per gli automi universali di Büchi/Co-Büchi.
Invece per le specifiche espresse tramite LTL, dato che il nuovo automa
universale di Büchi che ne accetti il modello richiede taglia esponenziale e che i giochi
LTL è stato dimostrato che ricadono nell'insieme dei problemi 2EXPTIME-hard,
decidere un grafo di gioco ricorsivo in LTL è un problema 2EXPTIME-complete.
86
Capitolo 6
Risolvere giochi ricorsivi con specifiche CARET usando strategie
modulari
6.1 Introduzione
In questo capitolo ci occuperemo di risolvere il problema di determinare
l'esistenza di una strategia modulare vincente per il Giocatore0 in un giochi ricorsivi
con condizione di vincita espressa esternamente al game graph attraverso una formula
CARET. Chiameremo questo tipo di gioco con la notazione (G,φ).
Il passo principale della soluzione proposta consiste nel ridurre un gioco siffatto
ad uno equivalente con condizioni di vincita interne espresse come una coppia
W'=(I,C) dove I è un sottoinsieme di vertici e C è una famiglia di sottoinsiemi di
vertici. Una strategia è vincente rispetto a (I,C) se e solo se, su ogni giocata che ne
deriva, almeno uno dei vertici in I si ripete infinite volte (condizione di Büchi) ed esiste
un insieme di C tale che nessuno dei suoi vertici si ripete infinite volte (condizione
generalizzata di Co-Büchi). Denotiamo questa classe di condizioni di vincita con il
nome Büchi-coBüchi. Con questa trasformazione intendiamo codificare nel game graph
parte delle specifiche espresse dalla formula. Dato un gioco (G,φ) dove φ è una formula
, costruiamo un gioco (G', W'), dove la taglia di G' è 2 O(|φ|) la taglia del gioco di
partenza G, e W' è una condizione di tipo Büchi-coBüchi. Il gioco (G,φ), e quindi, il
problema di decisione considerato per un gioco con condizioni di vincita espresse nella
logica CARET, si riduce allo stesso problema con condizioni interne del tipo BüchicoBüchi.
Per poter applicare correttamente la metodologia per la decidibilità dell'esistenza
di una strategia modulare trattata in [3], il cui passo fondamentale risiede nel costruire l'
automa two-way alternante di parità Awin, dovremmo trasformare le condizioni di
Büchi-coBüchi in condizioni di parità. Come, però, detto in precedenza C è una
87
famiglia di sottoinsiemi. Questo rende molto più complesso creare a partire da W'
condizioni di accettazione di parità equivalenti. Per rendere più agevole la definizione
delle condizioni di parità, abbiamo previsto un ulteriore passo intermedio, che generi un
nuovo gioco che chiameremo (G'',W'') in cui la condizione di accettazione sarà
W''=(I',C'), dove I' è un insieme di stati che vogliamo vedere infinitamente spesso e C'
è un insieme di stati che non vogliamo vedere infinitamente spesso. Chiameremo queste
condizioni come condizioni di tipo 1-Rabin. Ovviamente G'' e W'' saranno costruiti in
maniera tale da accettare gli stessi run riconosciuti su G' da W'. La semplificazione è
stata ottenuta tramite l'introduzione di un parametro j, il cui funzionamento e scopo
sono resi chiari nel paragrafo 6.3.
L'ultimo passo intermedio prima di definire Awin risulta tradurre le condizioni W''
di tipo 1-Rabin in una condizione di parità (che chiameremo colour). La funzione di
colorazione che preservi la corretta accettazione dei run è stata costruita in maniera
agevole e viene presentata nel dettaglio nel paragrafo 6.4.
Con un grafo di gioco con condizioni di parità come input, la costruzione
dell'automa two-way alternante Awin risulta essere immediata e richiede solo alcune
leggere modifiche rispetto alla procedura originaria concepita per giochi con condizioni
di accettazione esterne.
Sfruttando il risultato ottenuto in [42] possiamo dunque affermare di poter
convertire Awin in un automa ad albero one-way non-deterministico e considerare
l'intersezione fra quest'ultimo automa e l'albero delle strategie Astr, ottenendo una nuovo
automa A' che accetti un albero se e solo se corrisponde ad un albero di strategia
vincente.
6.2 Il processo di trasformazione da (G,φ) a (G',W')
A differenza dell'approccio utilizzato in [3], in cui le condizioni di vincita
potevano essere espresse tramite un automa esterno B, a partire da una formula CARET
non possiamo impiegare alcun automa equivalente che ne rappresenti il linguaggio
riconosciuto. Questo problema si verifica a causa del fatto che specifiche in logica
88
CARET definiscono linguaggi di tipo context free e i problemi come inclusione e vuoto
dell'intersezione con altri linguaggi context free (come quelli derivanti da RSM o RGG)
sono indecidibili[24]. Come avviene per la costruzione di macchine ricorsive con
condizioni interne CARET, per poter rendere il problema decidibile abbiamo bisogno che
anche il nostro modello e le nostre specifiche debbano essere sincronizzate sui simboli
call, ret e int.
L'introduzione della tecnica proposta in [3] nell'ambito dei giochi ricorsivi ha
però introdotto ulteriori problemi. L'etichettamento di un nodo può soddisfare diversi
tipi di assegnamenti di verità e se un nodo deve verificare un vincolo su un successore,
lo stesso successore potrebbe avere più di una configurazione di proprietà
proposizionalmente consistenti che soddisfano quel vincolo. Se un nodo ha più
configurazioni per un successore, in maniera non-deterministica l'automa esegue
contemporaneamente tutte le possibili assegnazioni. Per poter lavorare su i giochi,
abbiamo bisogno di determinizzare il non-determinismo e verificare se tutti i possibili
assegnamenti generano run accettanti. Per ogni nodo originario, nel nuovo gioco
creeremo tanti nodi quanti sono gli atomi di φ consistenti con il suo etichettamento. In
ogni vertice generato, le informazioni u e A indicheranno rispettivamente il nome del
nodo originario e l'atomo consistente con esso che viene considerato. Per poter
garantire che, una volta scelto un successore, tutti i suoi assegnamenti che rispettano la
semantica di CARET generino run accettanti, abbiamo dovuto introdurre dei nodi
intermedi di tipo AND (∧) che raggruppino e testino tutte le possibilità. L'insieme di
questi stati intermedi verrà indicato con il nome AndSucCAtoms.
Il grafo di gioco G' sarà costruito a partire dalla negazione della formula e
l'obiettivo che vogliamo raggiungere è che se esisteva un strategia vincente per il
giocatore0 sul gioco di partenza (G,φ), allora anche in (G',W') abbiamo una strategia
vincente per quel giocatore e viceversa.
Come veniva fatto nel caso degli RSM, per garantire che gli Until non restino
pendenti, abbiamo bisogno di definire delle condizioni di accettazione specifiche allo
scopo. Se consideriamo gli Until globali, lavorando con la negazione della formula,
vogliamo che per almeno uno di essi gli stati in cui quell'Until è correntemente
soddisfatto vengano visti un numero finito volte. Per ogni formula ψi dove ψi è una
89
delle formule di Until globale della chiusura di φ,
vi sarà un insieme Cψi che
comprenderà tutti gli stati che correntemente la soddisfano. Nelle condizioni di
accettazione avremmo dunque un OR di insiemi di stati Cψi su cui vorremmo verificare
condizioni generalizzate di Co-Büchi
Solo queste condizioni tuttavia non sono sufficienti. Restano infatti da gestire gli
Until astratti, che presentano un'ulteriore sfida. Infatti, quando viene effettuata una call
e vi sono degli Until abstract da verificare, dobbiamo assicurarci che anch'essi vengano
prima o poi soddisfatti. Se il controllo non dovesse più ritornare dal modulo chiamato,
qualunque formula di Until astratta non soddisfatta sul modulo chiamante resterebbe
pendente. Nella costruzione sulle macchine ricorsive, per affrontare questo problema
era stato inserito un tag inf/fin che aveva lo scopo di dire se i run a partire da un nodo
erano finiti o infiniti, e nel caso fossero stati infiniti, allora bisognava verificare che non
venissero visti infinite volte stati in cui c'erano Until astratti correntemente soddisfatti
(quindi pendenti). Per i giochi la situazione è più complessa: infatti non abbiamo idea di
come sono strutturati i run, poichè l'esecuzione dipende dal comportamento
dell'avversario.
Abbiamo dunque inserito nella nostra costruzione un nuovo tag, mustRtn, il cui
significato è leggermente diverso dal Tag previsto in [2]. Quando un nodo passa
mustRtn a un nodo successivo, gli sta dando una sorta di “indicazione” su come dovrà
avvenire l'esecuzione: se mustRtn è settato a 1, sta imponendo che, qualunque sia il
comportamento del giocatore universale, l'esecuzione prima o poi deve uscire dal
modulo; se mustRtn è settato a 0, l'esecuzione può sia uscire che non uscire dal modulo.
I nodi di entrata del modulo di partenza dovranno ovviamente avere il mustRtn uguale a
0, poiché vogliamo considerare i run infiniti.
La funzione di transizione deve però tenere conto di alcune regole che devono
vigere fra un nodo e il successore per rendere consistente il significato del valore
mustRtn rispetto alle meccaniche del sistema:
Da nodo a nodo interno:Il mustRtn viene passato da nodo a nodo interno senza nessun
cambiamento.. (regola 1)
Da ritorno a nodo: Quando si esce da un modulo, bisogna controllare che, se il modulo
chiamante aveva un valore di mustRtn, lo stesso valore di mustRtn deve essere presente
90
nel nodo che raggiungiamo dopo il return. (regola 2)
Da nodo a chiamata:
•
Caso 1 (modulo chiamante con mustRtn=0):Quando viene effettuata una call, il
nodo del modulo chiamante con mustRtn a 0 può non deterministicamente
decidere se chiamare il nodo di entrata del modulo chiamato con un mustRtn
uguale a 1, imponendo dunque che l'esecuzione del modulo chiamato debba
prima o poi uscire, oppure sceglierlo con un MustRtn uguale a 0, non ponendo
alcune restrizioni. (regola 3)
•
Caso 2 (modulo chiamante con mustRtn=1): Se il nodo del modulo chiamante ha
mustRtn uguale a 1, può invocare altri moduli solo con nodi di entrata con
mustRtn uguali a 1. (regola 4)
Il controllo dovrà dunque assicurare che, quando uno stato è taggato con
mustRtn a 0, gli stati di questo tipo vengano visti infinitamente spesso e questo copre
anche quelli in cui gli Until abstract potevano essere pendenti (ricordiamoci che stiamo
lavorando sulla negazione della formula). Dunque ogni stato in cui mustRtn è settato a
0 verrà inserito in un insieme I con condizioni alla Büchi.
Le nuove condizioni saranno dunque costituite da un insieme di stati I che
vogliamo vedere infinitamente spesso e da un OR di insiemi di stati Cψi che vogliamo
vedere finite volte. Questo vuol dire che verranno accettate solo quelle strategie che per
ogni play π in G' allora Inf(π) I∅ e ∃ i=1,2,...,n (dove n è il numero di formule
Until Globali nella chiusura di φ) tale che Inf(π) Cψi =∅ .
6.2.1 Costruzione Formale
Prima di iniziare a presentare le definizioni per il passaggio da (G,φ) a (G',W'),
presentiamo alcune funzioni di cui ci serviremo in seguito. Alcune vengono riprese da
[2] mentre altre sono funzioni nuove e introdotte allo scopo di semplificare la lettura e
la comprensione della costruzione:
91
– Atoms(φ): Insieme degli atomi di φ.
– AbsNextReq(A,A'): la relazione è vera se le specifiche di next con visibilità
locale richieste in A sono vere in A', cioè per ogni Xaφ'∈Cl(φ), Xaφ'∈A se e solo
se φ'∈A'
– GlNextReq(A,A'): la relazione è vera se le specifiche di next con visibilità
globale richieste in A sono vere in A', cioè per ogni Xgφ'∈Cl(φ), Xgφ'∈A se e solo
se φ'∈A'
– CallerFormulas(A): contiene tutte quelle formule in A che devono essere
rispettate sul vertice rappresentante l'ultima chiamata in attesa. Quindi
CallerFormulas(A)={X- φ | X- φ∈ A}
– Consistent(v,φ): sottoinsieme degli atomi di φ consistenti con l'etichettamento di
v
– Mod(v): funzione che restituisce il nome del modulo
Mod(v)=m sse v∈ Nm U Bm
– Type(v): funzione che restituisce il tipo (∧,∨ ) del nodo v. ∀ v ∈ Nm U Bm
allora:
Type (v)=∧ sse v∈ P1m dove m=Mod(v)
Type (v)=∨ sse v∈ P0m dove m=Mod(v)
6.2.2 Dati in input
Il grafo di partenza sarà un RGG G dove
G = (M, min,{Sm}mєM)
Dove:
•
M sono i nomi dei moduli
•
min è il nome del modulo iniziale
92
•
{Sm}mєMSm è l'insieme dei moduli
Per ogni modulo avremo una tupla Sm =(Nm, Bm, Ym, Enm, Exm, P0m, P1m, δm, ηm)
che ne definisce la struttura interna. Da notare che in questo caso, la funzione di
etichettamento per i nodi dei moduli assocerà ad ognuno di essi un sottoinsieme di
proposizioni valide su quel nodo. Dunque ηm: Nm→ 2AP.
In input avremmo inoltre la formula CARET che indicheremo con il simbolo φ.
6.2.3 Costruzione del gioco (G',W')
Il nuovo gioco (G',W') sarà costituito da un RGG G' e da una condizione di
vincita W' di tipo Büchi-coBüchi. G' è definito dalla tripla
G' = (M', min',{S'm}mєM' )
Dove:
•
M'=M è l'insieme dei nomi dei moduli,
•
min'=min è il modulo iniziale,
•
{S'm}mєM' è l'insieme dei moduli
Per come abbiamo descritto il funzionamento del paragrafo precedente, ci
aspettiamo che l'insieme W' sarà costituito da coppie (I, Cψi ) con i=1,...,|W'| dove per
ψi ci riferiremo a una delle funzioni di Until globali presenti in Cl(φ). La definizione di
tali condizioni sarà definita formalmente alla fine di questo paragrafo.
Per ogni modulo avremo la corrispettiva tupla S'm =(N'm, B'm, Y'm, En'm, Ex'm,
P0'm, P1'm, δ'm, η'm) che ne definirà la costruzione.
Chiameremo con V'm tutti i vertici di ogni modulo S'm, siano essi nodi o box.
93
I vertici che appartengono a N'm\Ex'm avranno una struttura del tipo
(u,A,type,mustRtn) dove:
•
u: è il nome del nodo nel grafo G
•
A: è uno degli atomi di φ proposizionalmente consistente con l'etichettamento di
u ( A∈ Atoms(φ), , A ∈ Consistent(u,φ))
•
type: è un tag {∧,∨} che ha lo scopo di indicare se il nodo apparteneva
rispettivamente o al giocatore1 o al giocatore0. (type=Type(u))
•
mustRtn: è un valore {0,1}. 1 indica che a partire dal quel nodo tutti i run devono
essere finiti (ossia devo sempre uscire dal modulo); 0 indica che i run da quel
nodo possono sia essere finiti che infiniti.
I vertici che appartengono a Ex'm (ossia i nodi di uscita dei moduli) saranno
identificati dalla tripla (x,A,R) dove:
•
x: è il nome del nodo o della box nel grafo G
•
A: è uno degli atomi di φ proposizionalmente consistente con l'etichettamento di
x (A∈Atoms(φ), ed A ∈ Consistent(x,φ))
•
R: è uno degli atomi di φ che tiene traccia delle formule di abstract requirements
che devono essere rispettate dai nodi dopo l'uscita dal modulo (R∈Atoms(φ))
I vertici che appartengono a B'm saranno identificati dalla quadrupla (b,A,type,
mustRtn) dove:
•
b: è il nome della box nel grafo G
•
A: è uno degli atomi di φ. (A∈Atoms(φ))
•
type: è un tag {∧,∨} che ha lo scopo di indicare se la box apparteneva
rispettivamente o al giocatore1 o al giocatore0. (type=Type(b))
•
mustRtn: è un valore {0,1}. 1 indica che la box è stata invocata da un vertice che
era su di un run che doveva essere finito. 0 indica che la box è stata invocata da
un vertice che era su di un run che poteva essere infinito o finito.
94
Ogni modulo del nuovo grafo di gioco sarà dunque costruito sulla base delle
seguenti regole:
•
L'insieme dei nodi N'm = {(u,A,type,mustRtn) | u∈Nm\Exm} ∪ {(x,A,R) |
x∈Exm, } ∪ AndSucCAtoms
dove
AndSucCAtoms={(u, A,∧, v, mustRtn) | u∈Nm, v ∈ Nm U Bm , v∈ δm(u),
A∈Atoms(φ), e mustRtn=0 oppure mustRtn=1 } è l'insieme dei nodi intermedi
•
B'm = {(b,A,type, mustRtn) | b∈Bm,}
•
Y'm (b, A)= (Ym(b))' è la funzione di associazione dei nomi delle box con i
rispettivi moduli
•
En'm = {(e, A, type, mustRtn) | e∈Enmin e mustRtn=0 }∪
∪ {(e, A, type, mustRtn) | e∈Enm con m≠min, e mustRtn=0 oppure mustRtn=1 }
sono i vertici di entrata (i vertici di entrata del modulo iniziale portano il valore
mustRtn uguale a zero perchè i play del gioco non dovrebbero essere finiti)
•
Ex'm ={(x,A,R) | x∈Exm} sono i vertici di uscita
•
P0'm, P1'm partizioni di (N'm\Ex'm) ∪ B'm dove:
P0'm= {(u, A, type, mustRtn) | m=Mod(u) e type=∨} ∪ {(b,A,type, mustRtn) |
m=Mod(b) e type=∨}
P1'm= {(u, A, type, mustRtn) | m=Mod(u) e type=∧} ∪ {(b,A,type, mustRtn) |
m=Mod(b) e type=∧}∪ AndSucCAtoms
•
η'm ((u, A, type, mustRtn)) = ηm(u) è l'etichettamento per i nodi interni,
η'm(((b,A,type, doesRtn),(e,A',type', mustRtn)))=ηm((b,e)) è l'etichettamento per
le call mentre η'm(((b,A,type, mustRtn),(x,A',R)))=ηm((b,x)) per i return
Le definizioni delle nuove Calls e Rtns saranno le seguenti
Calls'm= {((b,A,type,mustRtn), (e,A',type',mustRtn')) | b∈Bm ed e∈EnYm'(b)}
Rtns'm= {((b,A,type,mustRtn), (e, A', R)) | b∈Bm e x∈EnYm'(b)}
95
A questo punto dobbiamo ricostruire la funzione di transizione δ'm. Le regole
devono ovviamente rispettare la semantica di CARET e abbiamo modificato quelle
proposte originariamente dagli autori per adattarle al nostro scopo. La differenza
principale introdotta è che nel nostro caso abbiamo dovuto inserire i nuovi nodi AND
per gestire le varie configurazioni del nodo. La prima regola si occupa di connettere i
nodi AND ai loro corretti predecessori.
Le successive si occupano di connettere
correttamente questi nodi ai successori. Ricordiamo che i nodi intermedi contenuti
dall'insieme AndSucCAtoms sono strutturati nel seguente modo (u, A, ∧, v, mustRtn ),
dove u può essere o un nodo o un ritorno e v può essere un nodo interno, una chiamata
o una exit. A seconda del tipo di transizione, sono stati sviluppati controlli interno per
mantenere le regole di consistenza del valore mustRtn.
A questo punto possiamo definire la funzione di transizione δ'm' :
Da nodo a nodo in AndSucCAtoms: δ'm'((u, A, type, mustRtn)) conterrà (u, A, ∧, v,
mustRtn )
 ∀ (u, A, ∧, v, mustRtn )∈ AndSucCAtoms, se u∈Nm \Exm
Da nodo a nodo interno: se v è un nodo interno allora δ'm ((u, A, ∧, v, mustRtn))
conterrà (v, A', type, mustRtn') se e solo se:
•
GlNextReq(A, A') e AbsNextReq(A, A')
•
CallerFormulas(A)= CallerFormulas(A')
•
mustRtn= mustRtn' (regola 1 del mustRtn)
Da nodo a chiamata: se v=(b,e) allora δm' ((u, A,∧, v, mustRtn)) conterrà ((b, A', type',
mustRtn'),(e, A'', type'', mustRtn'')) se e solo se:
•
A' è proposizionalmente consistente con (b, e)
•
GlNextReq(A,A') e AbsNextReq(A,A')
•
CallerFormulas(A)= CallerFormulas(A')
96
•
GlNextReq(A',A'') e CallerFormulas(A'')={X-φ'∈Cl(φ) | φ'∈A}
•
Se mustRtn=1 allora mustRtn'=1 (regola 4 del mustRtn)
•
Se mustRtn=mustRtn''=0 e non c'è alcuna formula del tipo Xa φ in A' (regola 3
del mustRtn)
Da nodo a exit: se v è un nodo d'uscita allora δm' ((u, A, ∧, v, mustRtn)) conterrà (x, A',
R), dove x∈Exm, se e solo se:
•
GlNextReq(A,A'), AbsNextReq(A,A') e GlNextReq(A',R)
•
CallerFormulas(A)= CallerFormulas(A')
•
Non c'è alcuna formula Xa φ in A'
 ∀ (u, A, ∧, v, mustRtn )∈ AndSucCAtoms, dove u= (b,x) con x∈Exm , sia u'=(b,
A, type, mustRtn'),(x, A', R) allora:
Da ritorno a nodo: δm' (u, A, ∧, v, mustRtn ) conterrà (v, A'', type'', mustRtn'') se:
•
AbsNextReq(A,A') e CallerFormulas(A)= CallerFormulas(R)
•
R è proposizionalmente consistente con (b,x)
•
GlNextReq(R,A'') e AbsNextReq(R,A'')
•
CallerFormulas(R)= CallerFormulas(A'')
•
mustRtn''=mustRtn (regola 2 di mustRtn)
Restano da definire le condizioni di accettazione rappresentate da W'. Vengono
definiti i seguenti insiemi:
– Per ogni formula di Until globale ψi= φ1Ugφ2 in Cl(φ), c'è un insieme Cψi che
contiene tutti i vertici nella forma (u, A, type, mustRtn) o (x, A, R) o ((b, A,
mustRtn),(e, A', type,mustRtn')) o ((b,A',type,mustRtn) ,(x,A'',A)) dove A soddisfa
correntemente ψi.
– C'è un insieme I che contiene tutti gli stati (u, A, type, mustRtn) o ((b, A, type,
mustRtn),(e,
A',
type,mustRtn'))
o
((b,A',type,mustRtn)
,(x,A'',A))
dove
97
mustRtn=0.
Allora l'insieme W' sarà costituito nel seguente modo: W'= {(I, Cψi )| ψi= φ1Ugφ2}
6.3 Da condizioni di Büchi-coBüchi a condizioni 1-Rabin
Dalla costruzione precedente abbiamo ottenuto un gioco (G',W') in cui W'= {(I,
Cψi )| ψi= φ1Ugφ2}. L'indice i ovviamente può assumere valori da 1 a |W'| ed è una
sorta di “numerazione” delle varie formule di Until Globali. Per rendere più facilmente
convertibili queste condizioni in condizioni di parità, abbiamo eseguito un passo
intermedio per creare un unico insieme C che riconoscesse esattamente gli stessi play
accettati dall'OR dei vari insiemi Cψi.
Verrà definito dunque un nuovo gioco (G'',W'') dove W''=(I',C') è una condizione
di accettazione di tipo 1-Rabin e G'' è un RGG nei cui vertici viene inserita un'ulteriore
informazione: un parametro j. j è un numero che può spaziare da zero a |W'|. La
condizione che vogliamo rigettare è quella per cui per ognuno degli insiemi Cψi
vediamo uno stato ad esso appartenente in cui è ψi correntemente soddisfatta infinite
volte. Usando j siamo in grado di riferirci alla specifica formula di Until astratto ψj e
conseguentemente all'insieme Cψj determina.
Se consideriamo un vertice e un suo successore, la gestione del j dovrebbe essere
la seguente:
•
Se un nodo ha il valore j e soddisfa correntemente ψj, allora il nodo successivo
dovrà avere il valore j' uguale a j incrementato di 1.
•
Se j raggiunge il valore |W'| e si chiede un incremento, nel nodo successivo j
dovrà resettarsi.
•
Un vertice in cui j è uguale a zero, automaticamente fa una transizione verso se
stesso, con il valore di j uguale a uno.
Per come è controllato il valore j da un nodo a una altro, allora se esiste per ogni
98
insieme Cψj uno stato che infinite soddisfa correntemente ψj, allora avrò almeno uno
stato in G'' che ha j=0 che verrà visitato infinite volte. Dunque tutti gli stati che hanno
j=0 devono essere inseriti in un insieme C e con una condizione di Co-Büchi
imponiamo che vengano visti un numero finito di volte. L'insieme I' deve essere solo
ridefinito, recuperando quei vertici la cui etichetta era uno stato in I. Il nuovo RGG G''
avrà come condizione di accettazione l'insieme W'', costituito semplicemente da una
coppia (I',C'), e verranno accettate solo quelle strategie in cui tutti i play π rispettano le
condizioni Inf(π) I∅ e Inf(π) C=∅
6.3.1 Costruzione formale di (G'',W'')
In input avremmo un gioco (G',W') in cui
G' =(M', min',{S'm}mєM' )
W'= {(I, Cψi )| ψi= φ1Ugφ2}
Ogni modulo del grafo di gioco G' è rappresentato come
S'm =(N'm, B'm, Y'm, En'm, Ex'm, P0'm, P1'm, δ'm, η'm)
Definiremo un nuovo gioco (G'',W'') in cui G'' è definito dalla tripla
(M'', min'',{S''m}mєM'' )
Dove:
•
M''=M' è l'insieme dei nomi dei moduli,
•
min''=min' è il modulo iniziale,
•
{S'm}mєM'' è l'insieme dei moduli
e ogni modulo è rappresentato come
S''m =(N''m, B''m, Y''m, En''m, Ex''m, P0''m, P1''m, δ''m, η''m)
dove:
•
N''m: N'm  {0,1,..., |W'|}
•
B''m: B'm  {0,1,..., |W'|}
99
•
Y''m: per ogni elemento (b,j) allora Y''m(b,j)=Y'm(b)
•
En''m= {(u,k) | u∈En'm e j={0,1,..., |W'|}}
•
Ex''m= {(u,k) | u∈Ex'm e j={0,1,..., |W'|}}
•
P0''m= {(u,k) | u∈P0'm e j ={0,1,...,|W'|}}
•
P1''m= {(u,k) | u∈P1'm e j={0,1,...,|W'|}}
•
η''m:= per ogni elemento (u,j) allora η''m(u,j)=η'm(u)
Resta da definire la funzione di transizione δ''m:
Consideriamo (u',j) con u' = (u, A, type, mustRtn). Allora ∀ (u',j)∈N''m:
•
(u',j')∈ δ''m (u',j) con j=0 e j'=1;
Inoltre se A soddisfa correntemente ψj allora:
•
(v',j')∈ δ''m (u',j) con v' =(u, A', ∧, w, mustRtn ) con j≠0 e j'=(k+1)mod(|W|+1) e
∀ w' tale che w'∈ δ'm(v') allora (w',j')∈ δ''m (v',j').
Se A non soddisfa correntemente ψj allora:
•
(v',j)∈ δ''m (u',j) con v' =(u, A', ∧, w, mustRtn ) con j≠0 e∀ w' tale che w'∈
δ'm(v') allora (w',j)∈ δ''m (v',j).
La prima regola della funzione di transizione obbliga i nodi a non propagare il
valore j come zero. Tale accorgimento serve ad evitare l'occorrenza della seguente
situazione. Consideriamo un play che, dopo un numero finito di mosse, casualmente fa
arrivare il contatore a zero. Se da quel punto in poi nessun vertice rispetta le condizioni
per incrementare il valore j, allora tutti i nodi seguenti di questo play avranno j=0. Se il
run prosegue all'infinito in questa condizione, vedremo da un certo punto in poi tutti
stati con j=0 e questo play sarebbe dichiarato non accettante, anche se non abbiamo
visto infinitamente spesso almeno uno degli stati per ogni insieme Cψj (perchè nessuno
ha aggiornato il contatore).
La seconda regola contempla il caso in cui un vertice u' apparteneva all'insieme
Cψj . In questo caso lo stato deve aumentare il contatore in maniera tale che da quel
100
punto in poi possa venire rilevata la visita di uno stato in Cψj+1 e così via.
La condizione di accettazione sarà W''=(I',C') dove I'= {(u,j) | u∈Ι } e C'= {(u,j)
| (u,j)∈N''m e j=0} e un play π verrà accettato se e solo se rispetta le condizioni Inf(π)
I'∅ e Inf(π) C'=∅
6.4 Da condizioni 1-Rabin a condizioni di parità
Per tradurre la condizione di 1-Rabin definita in W'' con una condizione di parità
colour equivalente, ci siamo inizialmente chiesti quali caratteristiche devono avere i run
che sono accettati. Per come abbiamo definito I e C, risulta semplice comprendere che
per soddisfare le nostre condizioni:
•
Vogliamo vedere almeno uno stato in I infinite volte e tutti gli stati in C un
numero finito di volte (condizione A)
I casi che non sono ammissibili invece sono due:
•
Vedo infinitamente spesso uno stato in C (condizione B)
•
Non vedo infinitamente spesso gli elementi appartenenti a I. (condizione C)
Abbia dunque individuato tre tipologie di run, una ammissibile e le altre due non
ammissibili.
Dato che dare una condizione di parità implica dover dare una colorazione agli
stati in maniera tale che i run vengano accettati se il colore minimale visto
infinitamente spesso sia pari, è facilmente intuibile che i colori che dobbiamo usare
saranno {1,2,3}: un solo colore pari (per rappresentare la prima condizione che è quella
che caratterizza le partite valide) e due colori dispari (per escludere le due condizioni
non ammissibili).
Siano V''m = N''m∪Calls''m∪Rtns''m e V =UmєM''
V''m. Definiremo colour la
condizione di parità
colour :V''  {1,2,3}
nel seguente modo:
•
colour(x) = 3 ∀ x  V''\(I∪C) (condizione C)
101
•
colour(x) = 2 ∀ x  I\C (condizione A)
•
colour(x) = 1 ∀ x  C (condizione B)
La colorazione proposta traduce le condizioni di accettazione espresse con W'' poiché:
 Se un run era accettato con le condizioni W'', allora visitavo infinite volte uno
stato di I e nessuno di C. Per lo stesso run vedrò, dunque, infinitamente spesso il
colore 2 (e sicuramente non il colore 1) quindi, essendo pari il colore minimale,
la condizione di parità accetterà il run.
 Se il run non era accettato con le condizioni di W'', allora :
•
Visitavo un numero infinito di volte uno stato di C. Lo stesso run con le
nuove condizioni permetterà di vedere infinitamente spesso almeno uno
stato con il colore 1. Essendo dispari e minimale, la condizione di parità
respingerà il run.
•
Non visitavo un numero infinito di volte elementi di I. Questo vuol dire che
per lo stesso run, vedrò infinitamente spesso esclusivamente stati di colore 3
e, dato che tale colore è dispari, la condizione di parità respingerà il run.
Dunque basterà sostituire le condizioni W'' con la condizione colour per ottenere un
gioco (G'',colour) con condizioni di parità equivalente a (G'', W'') .
6.5 Costruire l'albero di strategia Astr
Da questo momento non consideriamo più come siamo arrivati al gioco ricorsivo
con condizioni di parità e qual'è la strutturazione interna dei nodi, ma ci riferiremo al
gioco ottenuto con la notazione (Gφ, colour) dove Gφ =(Mφ , minφ , {Sφm}mєMφ ). I
moduli saranno definiti dunque espressi come Sφm =(Nφm, Bφm, Yφm, Enφm, Exφm, P0φm,
P1φm, δφm, ηφm). Per ogni modulo, gli insiemi delle call e dei return verranno associati
rispettivamente a Callsφm, Rtnsφm. L'insieme dei vertici di ogni modulo sarà V φm = Nφm
∪ Callsφm ∪ Rtnsφm, mentre l'insieme di tutti i possibili vertici di ogni modulo sarà V
=UmєMφ Vφm. Sia ΔA l'insieme delle etichette {root,dummy} ∪ (V{⊤,⊥}).
In maniera simile all'approccio utilizzato in [3], l'albero di strategia è un ΔAlabelled k-tree che intende codificare una strategia modulare. La radice verrà sempre
102
etichettata con il simbolo root. Il sottoalbero i-esimo ad essa collegato codificherà
sempre la strategia modulare per il modulo i-esimo. Tutte le radici dei sottoalberi
corrispondenti a effettivi moduli verranno etichettate con il nome del nodo di entrata e
il simbolo ⊥. L'unica cosa che dobbiamo ricordare è che, nel caso i nodi di entrata dei
moduli siano più di uno, a prezzo di un esplosione della taglia del grafo al più cubica,
possiamo comunque sempre ridurre un grafo di gioco con moduli con entrate multiple a
uno con entrate singole, effettuando tante copie dello stesso modulo, ognuna con un
punto di entrata diverso, e cambiando le call e i return in maniera appropriata.
Per ogni nodo υ del k-tree appartenente sottoalbero m ed etichettato con la
coppia (v,⊤) allora:
•
Se v∈(Nφm\Exφm)Rtnsφm, allora tutti i nodi figli di υ prenderanno come
etichetta i valori dei successori di v insieme con la notazione ⊤/⊥. Inoltre se il nodo
v apparteneva a P0φm, allora si sceglie un nodo fra i successori di v e quel nodo
prenderà il valore ⊤ mentre tutti gli altri prenderanno ⊥. Se invece il nodo v
apparteneva a P1φm non sappiamo con certezza quale sarà il nodo scelto, dunque
dobbiamo taggare tutti i possibili successori con il valore ⊤.
•
Se v∈Callsφm, non codificheremo la chiamata ad altri moduli, ma metteremo dei
successori che corrispondano ai possibili ritorni dal nodo invocato, con associati una
qualsiasi notazione ⊤/⊥. In questo modo si codifica l'assuzione che la chiamata ad
un altro modulo non può finire in un return taggato con ⊥, qualunque siano le scelte
del giocatore universale. Tali condizioni verranno controllate successivamente per
mezzo delle avoid component di Awin.
•
Se v∈Exφm , allora vuole dire che siamo giunti in un nodo di uscita e non verrà
codificato alcun tipo di strategia dopo questo punto.
Ricordiamo infine che il se un vertice υ dell'albero è taggato con ⊤, allora
l'intero sottoalbero a partire da esso non sarà accessibile per effetto delle scelte che la
strategia ha effettuato. L'etichettamento per tutti i vertici di questo sottoalbero sarà
ovviamente dummy.
103
6.6 Costruzione dell'automa Awin con condizioni di parità e risoluzione del
problema di decidibilità
Finalmente possiamo introdurre l'automa two-way alternante con condizioni di
parità che riconosce alberi di strategia se e solo se questi sono alberi di strategia
vincenti per il gioco (Gφ, colour). L'automa sulla base del quale effettueremo la
costruzione non sarà più un automa che rappresenta le condizioni esterne, ma l'automa
Gφ .
Le funzioni che deve assolvere Awin sono esattamente le stesse individuate
precedentemente.
•
Deve simulare il comportamento di Gφ, in maniera tale da garantire che le
specifiche per quel determinato gioco siano soddisfatte. Il primo obiettivo è
simulare la funzione di transizione di Gφ. Inoltre quando ci si trova in un punto
di uscita, l'automa deve verificare che siano rispettate le condizioni delle avoid
component, questa volta memorizzate con un l'unico valore (x) che individuano
uscite dove non può finire il gioco. Le uscite nelle quali le partite del gioco non
possono terminare sono individuate a partire dall'albero di strategia Astr.
•
Deve garantire che le partite siano generate a partire dal nodo di entrata del
modulo iniziale e che non si possa uscire da tale modulo iniziale. Questa
condizione è posta principalmente con la motivazione che quelli che noi
vogliamo accettare sono solo le partite costituite da un numero infinito di mosse:
se potessimo uscire dal modulo iniziale, il gioco terminerebbe con un numero
finito di mosse e sarebbe valutato perdente per il giocatore esistenziale. A tale
scopo vengono usate nuovamente avoid component per indicare che non si può
uscire da alcun nodo di uscita del modulo min .
•
Deve accertarsi che quando l'uscita di un sottoalbero è etichettata con il
simbolo ⊥, l'invocazione da un altro modulo non dovrà uscire sicuramente in
quel nodo, indipendentemente da come si comporta il Giocatore1. Questo
avviene perchè abbiamo detto che parti delle computazioni possono essere
104
evitate per effetto delle mosse effettuate dalla strategia. Se tale ritorno fosse nel
modulo m all'uscita x, verificare tale condizione corrisponde a mandare una
copia dell'automa alla radice del sottoalbero corrispondente a m per ogni avoid
component della forma (x).
La condizione di vincita resta pressocchè inalterata. Ogni stato dell'automa
alternante viene mappato esattamente con il colore con cui veniva mappato il
corrispettivo stato nel gioco Gφ.
La taglia di Awin sarà O(|NφCallsφRtnsφ|2 ⋅|Exφ|) con Exφ insieme di tutte le
uscite di tutti i moduli di Gφ. La valutazione viene fatta ovviamente sulla base del caso
in cui i moduli abbiano un solo nodo di entrata.
Convertendo l'automa Awin in un automa one-way non-deterministico e
effettuando l'intersezione fra quest'ultimo e l'automa Astr, riusciremo infine ad ottenere
un nuovo automa A' one-way non-deterministico che accetta alberi se e solo se essi
corrispondono a alberi di strategia vincenti. Testare il vuoto dell'automa può essere
eseguito in tempo polinomiale nel numero degli stati ed esponenziale nel numero dei
colori della condizione di parità.
105
Conclusioni
L'obiettivo di questa tesi era risolvere il problema di decidere l'esistenza di una
strategia modulare vincente in giochi infiniti su macchine ricorsive quando avevamo
delle specifiche espresse per mezzo di una formula CARET.
Per comprendere meglio il problema che ci accingevamo a risolvere, sono stati
dedicati i primi capitoli alla trattazione dei concetti di base, come la definizione degli
ω-automi e delle condizioni di accettazione, che ricoprono un'importanza fondamentale
nel esprimere l'insieme delle partite vincenti di un gioco infinito, e il concetto di logica
temporale, analizzando le più comuni, come CTL e LTL.
Abbiamo successivamente presentato CARET, logica temporale creata allo scopo
di formalizzare proprietà su macchine ricorsive. La procedura di decisione per il
problema del model checking con specifiche di questo tipo ha rappresentato per questa
tesi una tecnica di costruzione fondamentale per poter calare questa realtà nel contesto
dei giochi su grafi ricorsivi e delle strategie modulari.
L'interesse per le strategie modulari è derivato dall'importanza che tecniche di
soluzione che rispettino tali caratteristiche hanno nella la progettazione di meccanismi
di controllo. Al di là, infatti, dello studio puramente teorico, la possibilità nella pratica
di realizzare moduli di un programma o di un sistema invocabili in qualsiasi tipo di
contesto rappresenta un obiettivo di estremo valore.
Fondendo i vincoli rilevati già nella procedura di decisione e le tecniche di
costruzione per la decidibilità di giochi ricorsivi con strategie modulari, siamo riusciti a
realizzare e formalizzare un processo di risoluzione anche per specifiche di tipo CARET,
basato sulla costruzione di un automa che accetti un albero se e solo se questo
corrisponde a un albero di strategia vincente per il Giocatore0. La metodologia descritta
si distanzia soprattutto nelle sue fasi iniziali in maniera sensibile dalle tecniche proposte
per condizioni esterne. La principale differenza che abbiamo dovuto valutare e gestire,
infatti, è stata generata dall'impossibilità di ottenere a partire da CARET delle specifiche
espresse tramite un automa esterno, poiché qualunque specifica CARET deve essere
sincronizzata sulla struttura dell'automa ricorsivo. L'approcio utilizzato in questa tesi ha
106
dovuto formulare una costruzione che permette di rendere interne le condizioni imposte
dalle specifiche, e solo successivamente raffinare tali condizioni ottenute per poter
renderle più semplicemente esprimibili attraverso automi alternanti, da cui poi
otterremo la costruzione dell'automa finale.
Gli sviluppi futuri a livello teorico per questo settore potrebbero risiedere non
solo nello studio di giochi ricorsivi con altre condizioni di accettazione, come quelle di
Rabin o Street o espresse come specifiche di altre logiche branching time, ma anche
nella fusione delle strategie modulari con altri tipi di contesti, come quello delle
strategie parziali, e la valutazione delle modifiche di approccio e complessità che la
coesistenza di diversi tipi di strategie possono determinare nella risoluzione di un unico
problema.
I limiti intrinsechi della metodologia proposta restano comunque legati alla
complessità di tale tecnica, che risulta essere ancora troppo elevata per poter permettere
di concretizzare tale approccio nell'effettiva implementazione di un tool di verifica.
L'analisi di casi specifici o di grafi con strutture molto ridotte potrebbe condurre gli
sbocchi della ricerca verso una direzione più pratica, permettendo di realizzare
algoritmi con una complessità accettabile, realizzando implementazioni più efficienti
anche con il supporto di tecniche basate sui BDD in combinazione con SAT solver.
107
Bibliografia
[1] R. Alur, K. Etessami, M. Yannakakis. Analysis of Recursive State Machines. In Proc.
Of CAV'01, LNCS 2102. Springer 2001
[2] R. Alur, K.Etessami, P. Madhusudan. A Temporal Logic of Nested Calls and
Returns. In Lecture Notes In computer Science, Volume 2988. 2004.
[3] R. Alur, S. La Torre, and P.Madhusudan, Modular Strategies for Infinite Games on
Recursive Graphs, in Proc.CAV 2003, 15th Internat. Conf. Computer Aided Verification,
Lecture Notes in Computer Science, vol 2725, Springer, Berlin. 2003
[4] R.Alur, S.La Torre, P. Madhusudan. Modular Strategies for Recursive Game
Graphs. In Proc. 9th Intern. Conf. On Tools and Algorithms for the Construction and the
Analysis of Systems, TACAS'03 LNCS 2619, Springer 2003
[5] T.Ball and S.Rajamani. Bebop: A Symbolic Model Checker for Boolean Programs.
SPIN Workshop on Model Checking of Software, LNCS 1885, 2000
[6] O. Bernholtz,
M. Y. Vardi , P. Wolper, An Automatha-theoretic Approach to
Branching-time model checking. In Computer Aided Verification, Proc. 6th Int.
Workshop, volume 828 of Lecture Notes in Computer Science, Stanford, California.
Springer-Verlag. 1994
[7] A. Boujjani, J. Esparza, O. Maler. Reachability Analysis of Pushdown Automata:
Applications to model checking. In CONCUR '97: Concurrency Theory, Eight
International Conference, LNCS 1243. Springer. 1997
[8] J. R. Büchi, Weak Second-order Arithmetic and Finite Automata, Mathematical
Logic Quarterly, 1960
[9] J.R. Büchi, L.H.G. Landweber, Solving Sequential Condition by Finite Strategies.
Trans.AMS 138. 1969
[10] J. R. Büchi, On Decision Method in Restricted Second Order Arithmetic, in Proc.
1960 Int.Congr. for Logic, Methodology and Philosophy of Science, Stanford Univ.
Press. Stanford 1962
[11] O. Bukart, B. Steffen. Model Checking for Context-free Processes. In CONCUR
108
'92: Concurrency Theory, LNCS 630. Springer 1992
[12] L.Burdy, Y. Cheon, D. Cok, M. Ernst, J.Kiniry, G.T. Leavens, R.Leino, and E. Poll.
An Overview of JML Tools and Application. In Proc. 8th International Workshop on
Formal Methods for Industrial Critical Systems. 2003
[13] T.Cachat. Symbolic Strategy Synthesis for Games on Pushdown Graphs, in
Proc.29th Internt. Colloq. Automata, Languages and Programming, ICALP' OP, Lecture
Notes in Computer Science, Vol.2380, 2002.
[14] T. Cachat. Two-Way Tree Automata Solving Pushdown Games. LNCS 2500
Springer 2002
[15] A. Chakrabarti, L.de Alfaro, T. Henzinger, M.Jurdzinski, F. Mang. Interface
Compatibility Checking for Software Modules. In Proc. 14th Intern.Conf.on Computer
Aided Verification, CAV'02 LNCS 2404, Springer 2002
[16] H.Chen and D.Wagner. Mops: An Infrastructure for Examining Security Properties
of Software. In Proceedings of ACM Conference on Computer and Communications
Security, 2002
[17] C. Courcoubetis, M. Vard , P. Wolper, M. Yannakakis, Memory-efficient
Algorithms for the Verification of Temporal Properties, Formal Methods in System
Design, 1992
[18] A. Eid, Finite ω-automata and Büchi automata, Dep.of Comp.Science, Illinois.
2009
[19] E.A.Emerson, E. M. Clarke. Charactering Correctness Properties of Parallel
Programs Using Fix-Points. In Proc. ICALP 1980
[20] J. Esparza, D.Hansel, P.Rossmanith, S.Schwoon. Efficient Algorithms for Model
Checking Pushdown Systems. In Computer Aided Verification, 12 th International
Conference, LNCS 1855. Springer 2000
[21] J. Esparza, A. Kucera, and S.S. Schwoon. Mode-checking LTL with Regular
Valuations for Pushdown Systems. Information and Computation, 2003
[22]B. Farwer, Ω-Automata, Springer-Verlang Berlin Heidelberg, 2002
[23] D.Gabbay, Temporal Logic: Mathematical Foundations and Computational
Aspects. NewYork Oxford University Press. 1994
[24] Hopcroft John E.; Motwani, Rajeev; Ullman, Jeffrey D., Automi, Linguaggi e
109
Calcolabilità, I ed. it., Addison Wesley
[25] O. Kupferman, N.Piterman, and M.Y. Vardi. Model Checking Linear Properties of
Prefix-Recognizable Systems. In Proc. Of CAV 02, LNCS 2404, 2002.
[26]L.H. Landweber. Decision problems for ω–automata. Mathematical Systems
Theory, 3:376–384, 1969.
[27] O. Lichtenstein, A.Pnueli. Checking That Finite State Concurrent Programs
Satisfy Their Linear Specification. In Proc. POPL 1985
[28] Z.Manna, A. Pnueli. The Modal Logic of Programs, Proc. of Int'l Colloquium on
automata languages and programming, lecture notes in Computer Science vol 71, 1979
[29] R. Mazala, Infinite Games, Springer-Verlang Berlin Heidelberg, 2002
[30] D.E. Muller, Infinite Sequences and Finite Machines. In: Proc. 4th IEEE Symp. On
Switching Circuit Theory and Logical Design 1963
[31] F. Nieβner, Nondeterministic Tree Automata, Springer-Verlang Berlin Heidelberg,
2002
[32] A. Pnueli, The Temporal Logic of Programs, Proc. 18th IEEE Symp. Of Foundation
of Computer Science
[33] A. Pnueli, R. Rosner, On the Synthesis of a Reactive Module. In Proc 16th ACM
Symposium on Principles of Programming Languages, Austin 1989
[34] M. O. Rabin. Decidability of Second order Theories and Automata on Infinite
Trees. Trans.Amer.Math. Soc. 141. 1969
[35] M. O. Rabin, Weakly Definable Relations and Special Automata, in: Mathematical
Logic and Foundations of Set Theory. North Holland. Amsterdam 1970.
[36]Michael O. Rabin, Decidability of Second-order Theories and Automata on Infinite
Trees, Transactions of the American Mathematical Society 141. 1969
[37] T. Reps, S.Horwitz, and S. Sagiv. Precise Interprocedural Dataflow Analysis via
Graph Reachability, in Proc. ACM POPL, 1995
[38] R. S. Street. Propositional Dynamic Logic of Looping and Converse. Inform.Contr.
1982
[39] W. Thomas. Infinite Games and Verification. In Proc. Internat. Conf. Computer
Aided Verification CAV'02, Lecture Notes in Computer Science Vol.2404. Springer,
Berling, 2002.
110
[40] J. F. Van Benthem, The Logic of Time, Dordrecht, D.Raile 1982
[41] M. Y. Vardi, An Automata-theoretic Approach to Linear Temporal Logic, Houston
1996
[42] M.Vardi. Reasoning About the Past with two-way Automata. In Proc. 25th Intern.
Coll. On Automata, Languages, and Programming, ICALP '98, LNCS 1443, Springer
1998
[43] M. Y. Vardi and P. Wolper. An Automata-theoretic Approach to Automatic Program
verification. In Proc. First IEEE Symposium on Logic in Computer Science, 1986.
[44] M. Y. Vardi, P. Wolper. Reasoning about Infinite Computations. Information and
computation. 1994
[45] I. Walekiewicz. Pushdown Processes: Games and Model-checking. Information
and Computation, 164(2): 234-263, 2001.
[46] P. Wolper, Constructing Automata from Temporal Logic Formulas: a Tutorial.
Lectures on formal methods and performance analysis: first EEF/Euro summer school
on trends in computer science. Springer-Verlag. New York. 2002.
111