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 i1∈ 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 , i1∈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 ∈labs false se p ∉lab s • run σ = s0, s1, s2, …, sn, … ∀ i s i , s i1 ∈ 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 c1 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 i1∈E ∀ i∈ (prende il nome di partita infinita) • Una sequenza finita ρ = v0,v1,v2,... vn∈ V+ con v i , v i1∈E ∀ in 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 , udove 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