1\chapter{Strumenti di dimostrazione: Logica Proposizionale} 2\label{chap:proof-tools} 3 4\newcommand{\naive}{na\"\i{}ve} 5 6Gli utenti di \HOL{} possono creare i loro propri strumenti di dimostrazione 7combinando le regole e le tattiche predefinite. La disciplina dei tipi \ML{} 8assicura che solo metodi logicamente validi possono essere usati per creare valori 9di tipo \ml{thm}. In questo capitolo, � descritto un esempio reale. 10 11Sono date due implementazioni dello strumento per illustrare vari stili 12di programmazione della dimostrazione. La prima implementazione � quella ovvia, ma 13� inefficiente a causa del metodo di `forza bruta' utilizzato. La seconda 14implementazione tenta di essere molto pi� intelligente. 15Sono anche discusse estensioni agli strumenti per permettere un'applicabilit� 16pi� generale. 17 18Il problema da risolvere � quello di decidere la verit� di una formula 19chiusa della logica proposizionale. Una tale formula ha la forma generale 20\[ 21\begin{array}{ccl} 22\varphi & ::= & v \;|\;\neg\varphi\;|\;\varphi 23\land \varphi \;|\; \varphi \lor \varphi \;|\; \varphi \Rightarrow 24\varphi\;|\;\varphi = \varphi 25\\[1ex] 26\mathit{formula} &::= & \forall \vec{v}. \;\varphi 27\end{array} 28\] 29dove le variabili $v$ sono tutte di tipo booleano, e dove il 30quantificatore universale al livello pi� esterno cattura tutte le 31variabili libere. 32 33\section{Metodo 1: Tabelle di verit�} 34 35\setcounter{sessioncount}{0} 36 37Il primo metodo che deve essere implementato � il metodo di forza bruta di tentare 38tutte le combinazioni booleane possibili. L'unico vero vantaggio di questo approccio 39� che � eccezionalmente semplice da implementare. Prima dimostreremo 40il teorema motivante: 41\begin{hol} 42\begin{verbatim} 43 val FORALL_BOOL = prove( 44 ``(!v. P v) = P T /\ P F``, 45 SRW_TAC [][EQ_IMP_THM] THEN Cases_on `v` THEN SRW_TAC [][]); 46\end{verbatim} 47\end{hol} 48La dimostrazione procede dividendo il goal in due met�, mostrando 49\[ 50(\forall v. \;P(v))\Rightarrow P(\top) \land P(\bot) 51\] 52(Goal che � automaticamente mostrato dal semplificatore), e 53\[ 54P(\top) \land P(\bot) \Rightarrow P(v) 55\] 56per una variabile booleana arbitraria $v$. Dopo lo splitting dei casi su $v$, 57le assunzioni sono poi sufficienti a mostrare il goal. (Questo teorema � 58di fatto gi� dimostrato nella teoria \theoryimp{bool}.) 59 60Il prossimo, e ultimo passo, � di riscrivere con questo teorema: 61\begin{hol} 62\begin{verbatim} 63 val tautDP = SIMP_CONV bool_ss [FORALL_BOOL] 64\end{verbatim} 65\end{hol} 66 67Questo permette il seguente 68 69\begin{session} 70\begin{verbatim} 71- tautDP ``!p q. p /\ q /\ ~p``; 72> val it = |- (!p q. p /\ q /\ ~p) = F : thm 73 74- tautDP ``!p. p \/ ~p`` 75> val it = |- (!p. p \/ ~p) = T : thm 76\end{verbatim} 77\end{session} 78e anche il pi� leggermente intimidatorio 79\begin{session} 80\begin{verbatim} 81- time tautDP 82 ``!p q c a. ~(((~a \/ p /\ ~q \/ ~p /\ q) /\ 83 (~(p /\ ~q \/ ~p /\ q) \/ a)) /\ 84 (~c \/ p /\ q) /\ (~(p /\ q) \/ c)) \/ 85 ~(p /\ q) \/ c /\ ~a``; 86runtime: 0.147s, gctime: 0.012s, systime: 0.000s. 87> val it = 88 |- (!p q c a. 89 ~(((~a \/ p /\ ~q \/ ~p /\ q) /\ (~(p /\ ~q \/ ~p /\ q) \/ a)) /\ 90 (~c \/ p /\ q) /\ (~(p /\ q) \/ c)) \/ ~(p /\ q) \/ c /\ ~a) = 91 T : thm 92\end{verbatim} 93\end{session} 94 95Questo � un algoritmo terribile per risolvere questo problema. La funzione\linebreak 96\ml{tautLib.TAUT\_CONV} incorporata nel sistema risolve il problema di sopra 97molto pi� velocemente. L'unico merito 98reale in questa soluzione � che richiede di scrivere una sola riga. Questa � 99un'illustrazione generale del fatto che gli strumenti di alto livello di \HOL{}, 100in particolare il semplificatore, possono fornire prototipi veloci per una variet� 101di compiti di dimostrazione. 102 103\section{Metodo 2: l'Algoritmo DPLL} 104 105Il metodo Davis-Putnam-Loveland-Logemann~\cite{DPLL-paper} per 106decidere la soddisfacibilit� di formule proposizionali in CNF 107(Conjunctive Normal form) � una tecnica potente, ancora oggi usata nei 108risolutori dell'ultima generazione. Se eliminiamo i quantificatori universali 109dalle nostre formule di input, il nostro compito pu� essere visto come determinare 110la validit� di una formula proposizionale. Testare la negazione di una tale 111formula per la soddisfacibilit� � un test per la validit�: se la negazione 112della formula � soddisfacibile, allora non � valida (l'assegnazione 113che soddisfa render� l'originale falsa): se la negazione della formula � 114insoddisfacibile, allora la formula � valida (nessuna assegnazione la pu� rendere 115falsa). 116 117\smallskip 118\noindent 119(Il codice sorgente per questo esempio � disponibile nel file \texttt{examples/dpll.sml}.) 120 121\subsection*{Preliminari} 122 123Per iniziare, assumiamo che abbiamo gi� del codice per convertire formule 124arbitrarie in CNF{}, e per poi decidere la soddisfacibilit� di queste 125formule. Assumiamo inoltre che se l'input all'ultima procedura � 126insoddisfacibile, allora restituir� un teorema della forma 127\[ \vdash \varphi = \holtxt{F} 128\] 129o se � soddisfacibile, allora restituir� un'assegnazione che soddisfa, 130un mapping da variabili a booleani. Questo mapping sar� una funzione da 131variabili \HOL{} a uno dei termini \HOL{} \holtxt{T} o \holtxt{F}. 132Cos�, assumeremo 133\begin{hol} 134\begin{verbatim} 135 datatype result = Unsat of thm | Sat of term -> term 136 val toCNF : term -> thm 137 val DPLL : term -> result 138\end{verbatim} 139\end{hol} 140(Il teorema restituito da \ml{toCNF} uguaglier� il termine di input a 141un altro in CNF{}.) 142 143\smallskip 144\noindent 145Prima di guardare all'implementazione di queste funzioni, dovremo 146considerare 147\begin{itemize} 148\item come trasformare i nostri input per adattarsi alla funzione; e 149\item come usare gli output dalle funzioni per produrre i nostri risultati 150 desiderati 151\end{itemize} 152 153Stiamo assumendo che il nostro input sia una formula quantificata universalmente. 154Entrambe le procedure CNF e DPLL si aspettano formule senza quantificatori. 155Vogliamo anche passare a queste procedure la negazione della formula 156originaria. Entrambe la manipolazioni di termini richieste si possono fare 157con le funzioni che si trovano nella struttura \ml{boolSyntax}. (in generale, 158le teorie importanti (come \theoryimp{bool}) sono accompangnate da 159moduli \ml{Syntax} che contengono funzioni per manipolare le 160forme di termini associate con quella teoria.) 161 162In questo caso abbiamo bisogno delle funzioni 163\begin{hol} 164\begin{verbatim} 165 strip_forall : term -> term list * term 166 mk_neg : term -> term 167\end{verbatim} 168\end{hol} 169La funzione \ml{strip\_forall} spoglia un termine di tutte le sue quantificazioni 170universali pi� esterne, restituendo la lista delle variabili spogliate 171e il corpo della quantificazione. La funzione \ml{mk\_neg} prende un 172termine di tipo \holtxt{bool} e restituisce il termine corrispondente alla sua 173negazione. 174 175Usando queste funzioni, � facile vedere come saremo in grado di prendere 176$\forall\vec{v}.\;\varphi$ come input, e passare il termine $\neg\varphi$ 177alla funzione \ml{toCNF}. Una domanda pi� significativa � come 178usare i risultati di queste chiamate. La chiamata a \ml{toCNF} restituir� un 179teorema 180\[ 181\vdash \neg\varphi = \varphi' 182\] 183La formula $\varphi'$ � ci� che passeremo poi a \ml{DPLL}. (Possiamo 184estrarla usando le funzioni \ml{concl} e \ml{rhs}.) Se 185\ml{DPLL} restituisce il teorema $\vdash \varphi' = \holtxt{F}$, 186un'applicazione di \ml{TRANS} a questo e al teorema mostrato di sopra 187deriver� la formula $\vdash \neg\varphi = F$. Al fine di derivare il 188risultato finale, avremo bisogno di trasformare questo in $\vdash\varphi$. Questo 189si ottiene meglio fornendo un teorema fatto su misura che incorpora l'uguaglianza (non 190ce n'� gi� uno simile nel sistema): 191\begin{hol} 192\begin{verbatim} 193 val NEG_EQ_F = prove(``(~p = F) = p``, REWRITE_TAC []); 194\end{verbatim} 195\end{hol} 196Per trasformare $\vdash \varphi$ in $\vdash (\forall 197\vec{v}.\;\varphi) = \holtxt{T}$, eseguiremo la seguente dimostrazione: 198\[ 199\infer[\texttt{\scriptsize EQT\_INTRO}]{ 200 \vdash (\forall \vec{v}.\;\varphi) = \holtxt{T}}{ 201 \infer[\texttt{\scriptsize GENL}(\vec{v})]{\vdash \forall \vec{v}.\;\varphi}{ 202 \vdash \varphi 203 } 204} 205\] 206L'altra possibilit� � che \ml{DPLL} restituir� un'assegnazione che soddisfa 207dimostrando che $\varphi'$. Se questo � 208il caso, vogliamo mostrare che $\forall\vec{v}.\;\varphi$ � falsa. 209Possiamo fare questo assumendo questa formula, e specializzando le 210variabili quantificate universalmente in linea con il mapping fornito. In 211questo modo, sar� possibile produrre il teorema 212\[ 213\forall \vec{v}.\;\varphi \vdash \varphi[\vec{v} := \mbox{\emph{satisfying 214 assignment}}] 215\] 216Poich� non ci sono variabili libere in $\forall\vec{v}.\;\varphi$, la 217sostituzione produrr� una formula booleana completamente ground. Questa 218si riscriver� direttamente a \holtxt{F} (se l'assegnazione 219rende $\neg\varphi$ vero, deve rendere $\varphi$ falso). Trasformare 220$\phi\vdash \holtxt{F}$ in $\vdash \phi = \holtxt{F}$ � questione di 221richiamare \ml{DISCH} e poi di riscrivere con il teorema incorporato 222\ml{IMP\_F\_EQ\_F}: 223\[ 224\vdash \forall t.\;t \Rightarrow \holtxt{F} = (t = \holtxt{F}) 225\] 226Mettendo tutto ci� che sta di sopra insieme, possiamo scrivere la nostra funzione wrapper, 227che chiameremo \ml{DPLL\_UNIV}, con il suffisso \ml{UNIV} 228che ci ricorda che l'input deve essere quantificato universalmente. 229\begin{hol} 230\begin{verbatim} 231 fun DPLL_UNIV t = let 232 val (vs, phi) = strip_forall t 233 val cnf_eqn = toCNF (mk_neg phi) 234 val phi' = rhs (concl cnf_eqn) 235 in 236 case DPLL phi' of 237 Unsat phi'_eq_F => let 238 val negphi_eq_F = TRANS cnf_eqn phi'_eq_F 239 val phi_thm = CONV_RULE (REWR_CONV NEG_EQ_F) negphi_eq_F 240 in 241 EQT_INTRO (GENL vs phi_thm) 242 end 243 | Sat f => let 244 val t_assumed = ASSUME t 245 fun spec th = 246 spec (SPEC (f (#1 (dest_forall (concl th)))) th) 247 handle HOL_ERR _ => REWRITE_RULE [] th 248 in 249 CONV_RULE (REWR_CONV IMP_F_EQ_F) (DISCH t (spec t_assumed)) 250 end 251 end 252\end{verbatim} 253\end{hol} 254 255La funzione ausiliaria \ml{spec} che � usata nel secondo caso 256si basa sul fatto che \ml{dest\_forall} sollever� un'eccezione 257\ml{HOL\_ERR} se il termine a cui � applicata non � quantificato universalmente. 258Quando l'argomento di \ml{spec} non � quantificato universalmente, questo significa 259che la ricorsione � conclusa, e tutte le variabili universali della 260formula originaria sono state specializzate. Quindi la formula risultante 261pu� essere riscritta a falso (le riscritture incorporate in \ml{REWRITE\_RULE} 262gestiscono tutti i casi necessari). 263 264Anche la funzione \ml{DPLL\_UNIV} usa \ml{REWR\_CONV} in due posti. 265La funzione \ml{REWR\_CONV} applica una singola riscrittura (al primo ordine) al 266livello principale di un termine. Questi usi di \ml{REWR\_CONV} sono fatti con 267chiamate alla funzione \ml{CONV\_RULE}. Questa esegue [lifts ndt] un conversione $c$ (una 268funzione che prende un termine $t$ e che produce un teorema $\vdash t = t'$), 269cos� che $\ml{CONV\_RULE}\;c$ porta il teorema $\vdash t$ a $\vdash t'$. 270 271 272\subsection{Conversione in Forma Normale Congiuntiva} 273\label{sec:conv-conj-norm} 274 275Una formula in Forma Normale Congiuntiva � una congiunzione di disgiunzioni 276di letterali (cio� o variabili, o variabili negate). E' possibile 277convertire formule della forma che ci attendiamo in CNF semplicemente 278riscrivendo con il seguenti teoremi 279\begin{eqnarray*} 280\neg (\phi \land \psi) &=& \neg\phi \lor \neg\psi\\ 281\neg (\phi \lor \psi) &=& \neg\phi \land \neg\psi\\ 282\phi \lor (\psi \land \xi) &=& (\phi \lor \psi) \land (\phi \lor \xi)\\ 283(\psi \land \xi)\lor\phi \ &=& (\phi \lor \psi) \land (\phi \lor 284\xi)\\[1ex] 285\phi \Rightarrow\psi &=& \neg\phi \lor \psi\\ 286(\phi = \psi) &=& (\phi \Rightarrow \psi) \land (\psi \Rightarrow 287\phi) 288\end{eqnarray*} 289Sfortunatamente, usare questi teoremi come riscritture pu� risultare in un 290incremento esponenziale nella dimensione di una formula. (Si consideri di usarli 291per convertire un input in Forma Normale Disgiuntiva, una disgiunzione 292di congiunzioni di letterali, in CNF{}.) 293 294Un approccio migliore � di convertire in ci� che � conosciuta come una ``CNF 295definizionale''. \HOL{} include funzioni per fare questo nella structure 296\ml{defCNF}. Sfortunatamente, questo approccio aggiunge quantificatori, esistenziali, 297extra alla formula. Per esempio 298\begin{session} 299\begin{verbatim} 300- defCNF.DEF_CNF_CONV ``p \/ (q /\ r)``; 301> val it = 302 |- p \/ q /\ r = 303 ?x. (x \/ ~q \/ ~r) /\ (r \/ ~x) /\ (q \/ ~x) /\ (p \/ x) : thm 304\end{verbatim} 305\end{session} 306Sotto la \holtxt{x} legata esistenzialmente, il codice ha prodotto una 307formula in CNF{}. Con esempio piccolo come questo, la formula � di fatto 308pi� grande di quella prodotto dalla traduzione ingenua, ma con esempi 309pi� realistici, la differenza diventa velocemente significativa. L'ultimo 310esempio usato con \ml{tautDP} � 20 volte pi� grande quando tradotto 311ingenuamente di quando usando \ml{defCNF}, e la traduzione richiede 150 312volte pi� tempo per essere eseguita. 313 314Ma cosa facciamo di queste variabili quantificate esistenzialmente extra? Di fatto, 315possiamo ignorare la quantificazione quando chiamiamo la procedura core DPLL. 316Se passiamo il corpo non quantificato a DPLL, otteniamo indietro o un 317verdetto di insoddisfacibilit� della forma $\vdash \varphi' = \holtxt{F}$, o una 318assegnazione per tutte le variabili libere che soddisfa. Se capita la 319seconda, la stessa assegnazione soddisfer� anche 320l'originale. Se capita la prima, eseguiremo la seguente dimostrazione 321\[ 322\infer{\vdash (\exists \vec{x}.\;\varphi') = \holtxt{F}}{ 323 \infer{\vdash (\exists \vec{x}.\;\varphi') \Rightarrow \holtxt{F}}{ 324 \infer{\vdash\forall \vec{x}.\;\varphi' \Rightarrow \holtxt{F}}{ 325 \infer{\vdash\varphi' \Rightarrow \holtxt{F}}{ 326 \vdash \varphi' = \holtxt{F} 327 } 328 } 329 } 330} 331\] 332producendo un teorema della forma attesa dalla nostra funzione 333\ml{wrapper}. 334 335Di fatto, c'� una funzione alternativa nell'API \ml{defCNF} che 336useremo preferendola a \ml{DEF\_CNF\_CONV}. Il problema con 337\ml{DEF\_CNF\_CONV} � che pu� produrre una grande quantificazione, 338che coinvolge molte variabili. Useremo piuttosto 339\ml{DEF\_CNF\_VECTOR\_CONV}. Al posto di un output della forma 340\[ 341\vdash \varphi = (\exists \vec{x}.\; \varphi') 342\] 343questa seconda funzione produce 344\[ 345\vdash \varphi = (\exists (v : \textsf{num} \rightarrow 346\textsf{bool}).\; \varphi') 347\] 348dove le variabili individuali $x_i$ della prima formula sono sostituite 349da chiamate alla funzione $v$ $v(i)$, e c'� una sola variabile 350quantificata, $v$. Questa variabile non influenzer� l'operazione della 351dimostrazione abbozzata di sopra. E fino a quando non richiediamo che i letterali siano 352variabili o le loro negazioni, ma permettiamo anche che siano termini della 353forma $v(i)$ e $\neg v(i)$, allora anche l'azione della procedura 354DPLL sulla formula $\varphi'$ non sar� influenzata. 355 356Sfortunatamente per uniformit�, in casi semplici, le funzioni di conversione 357a una CNF definizionale possono risultate in nessuna quantificazione 358esistenziale del tutto. Questo rende la nostra implementazione di \ml{DPLL} 359in qualche modo pi� complicata. Calcoliamo una variabile \ml{body} che 360sar� passata in cima alla funzione \ml{CoreDPLL}, esattamente come una 361funzione \ml{transform} che trasformer� un risultato di insoddisfacibilit� 362in qualcosa della forma desiderata. Se il risultato della conversione a 363CNF produce una quantificazione esistenziale, usiamo la dimostrazione abbozzata 364di sopra. Altrimenti, la trasformazione pu� essere la funzione identit�, 365\ml{I}: 366\begin{hol} 367\begin{verbatim} 368 fun DPLL t = let 369 val (transform, body) = let 370 val (vector, body) = dest_exists t 371 fun transform body_eq_F = let 372 val body_imp_F = CONV_RULE (REWR_CONV (GSYM IMP_F_EQ_F)) body_eq_F 373 val fa_body_imp_F = GEN vector body_imp_F 374 val ex_body_imp_F = CONV_RULE FORALL_IMP_CONV fa_body_imp_F 375 in 376 CONV_RULE (REWR_CONV IMP_F_EQ_F) ex_body_imp_F 377 end 378 in 379 (transform, body) 380 end handle HOL_ERR _ => (I, t) 381 in 382 case CoreDPLL body of 383 Unsat body_eq_F => Unsat (transform body_eq_F) 384 | x => x 385 end 386\end{verbatim} 387\end{hol} 388dove dobbiamo ancora implementare la procedura DPLL core (chiamata 389\ml{CoreDPLL} di sopra). Il codice di sopra usa \ml{REWR\_CONV} con il 390teorema \ml{IMP\_F\_EQ\_F} per influenzare due delle trasformazioni 391della dimostrazione. La funzione \ml{GSYM} � usata per invertire 392le uguaglianza di primo livello di un teorema. Infine, 393la conversione \ml{FORALL\_IMP\_CONV} prende un termine della forma 394\[ 395\forall x.\;P(x) \Rightarrow Q 396\] 397e restituisce il teorema 398\[ 399\vdash (\forall x.\;P(x) \Rightarrow Q) = ((\exists 400x.\;P(x))\Rightarrow Q) 401\] 402 403 404 405 406\subsection{La Procedura DPLL Core} 407\label{sec:dpll-procedure} 408 409La procedura pu� essere vista come una piccola variazione sulla tecnica 410di base ``tabelle di verit�'' che abbiamo gi� visto. Come con quella 411procedura, l'operazione core � un case-split su una variabile booleana. 412Ci sono tuttavia due differenze significative: DPLL pu� essere vista come 413una ricerca per un'assegnazione soddisfacente, cos� che se selezionando una variabile 414in modo da avere un particolare valore risulta in un'assegnazione soddisfacente, non abbiamo 415bisogno di controllare anche cosa accade se alla stessa variabile � dato il 416valore di verit� opposto. In secondo luogo, DPLL fa una certa attenzione a selezionare delle variabili 417opportune su cui fare lo split. In particolare � usata la \emph{unit propagation} 418per eliminare variabili che non causeranno ramificazioni nello 419spazio di ricerca. 420 421La nostra implementazione della procedura DPLL core � una funzione che prende 422un termine e restituisce un valore del tipo \ml{result}: o un teorema 423che uguaglia il termine originario a falso, o un'assegnazione soddisfacente (nella 424forma di una funzione da termini a termini). Mentre la ricerca DPLL per 425un'assegnazione soddisfacente procede, � costruita in modo incrementale 426un'assegnazione. Questo suggerisce che il nucleo ricorsivo della nostra funzione 427avr� bisogno di prendere un termine (la formula corrente) e un contesto (l'assegnazione 428corrente) come parametri. L'assegnazione pu� essere rappresentata in modo 429naturale come un insieme di equazioni, dove ciascuna equazione � o either $v = 430\holtxt{T}$ o $v = \holtxt{F}$. 431 432Questo suggerisce che una rappresentazione per la dichiarazione del nostro programma � 433un teorema: le ipotesi rappresenteranno l'assegnazione, e la conclusione 434pu� essere la formula corrente. Naturalmente, i teoremi \HOL{} 435non possono semplicemente essere desiderati esistere. In questo caso, possiamo rendere 436ogni cosa valida assumendo anche la formula iniziale. Cos�, quando 437iniziamo la nostra dichiarazione iniziale sar� $\phi\vdash\phi$. Dopo aver effettuato lo splitting sulla 438$v$, genereremo due nuove dichiarazioni 439$\phi,(v\!=\!\holtxt{T})\vdash \phi_1$, e 440$\phi,(v\!=\!\holtxt{F})\vdash \phi_2$, dove i $\phi_i$ sono il 441risultato di semplificare $\phi$ sotto l'assunzione aggiuntiva 442che vincola $v$. 443 444Il modo pi� semplice per aggiungere un'assunzione a un teorema � usare la 445regola \ml{ADD\_ASSUM}. Ma in questa situazione, vogliamo anche 446semplificare la conclusione del teorema con la stessa assunzione. Questo 447significa che sar� sufficiente riscrivere con il teorema 448$\psi\vdash\psi$, dove $\psi$ � la nuova assunzione. L'azione di 449riscrivere con un tale teorema far� apparire la nuova assunzione 450tra le assunzioni del risultato. 451 452La funzione \ml{casesplit} � cos�: 453\begin{hol} 454\begin{verbatim} 455 fun casesplit v th = let 456 val eqT = ASSUME (mk_eq(v, boolSyntax.T)) 457 val eqF = ASSUME (mk_eq(v, boolSyntax.F)) 458 in 459 (REWRITE_RULE [eqT] th, REWRITE_RULE [eqF] th) 460 end 461\end{verbatim} 462\end{hol} 463 464Un case split pu� risultare in una formula che � stata riscritta fino al 465vero o al falso. Questi sono i casi base della ricorsione. Se la 466formula � stata riscritta a vero, allora abbiamo trovato un'assegnazione 467soddisfacente, una che � ora archiviata per noi nelle ipotesi del 468teorema stesso. La seguente funzione \ml{mk\_satmap}, estrae 469quelle ipotesi in una map finita, e poi restituisce la funzione 470di lookup per quella map finita. 471\begin{hol} 472\begin{verbatim} 473 fun mk_satmap th = let 474 val hyps = hypset th 475 fun foldthis (t,acc) = let 476 val (l,r) = dest_eq t 477 in 478 Binarymap.insert(acc,l,r) 479 end handle HOL_ERR _ => acc 480 val fmap = HOLset.foldl foldthis (Binarymap.mkDict Term.compare) hyps 481 in 482 Sat (fn v => Binarymap.find(fmap,v) 483 handle Binarymap.NotFound => boolSyntax.T) 484 end 485\end{verbatim} 486\end{hol} 487La funzione \ml{foldthis} di sopra aggiunge le equazioni che sono archiviate come 488ipotesi nella map finita. La gestione delle eccezione in 489\ml{foldthis} � necessario perch� una delle ipotesi sar� la 490formula originaria. La gestione delle eccezioni nella funzione che cerca 491i binding delle variabili � necessaria perch� una formula pu� essere ridotta a 492vero senza che tutte le variabili siano state assegnate a un valore qualsiasi, cos� 493mappiamo in modo arbitrario tali variabili a \holtxt{T}. 494 495Se la formula � stata riscritta a falso, allora possiamo semplicemente restituire 496questo teorema direttamente. Un tale teorema non � del tutto nella forma giusta 497per il chiamante esterno, che si aspetta un'equazione, cos� se il 498risultato finale � della forma $\phi\vdash \holtxt{F}$, dovremo 499trasformare questo in $\vdash \phi = \holtxt{F}$. 500 501La domanda successiva da affrontare � cosa fare con i risultati delle 502chiamate ricorsive. Se un case split restituisce un'assegnazione soddisfacente questa 503pu� essere restituita senza modifiche. Ma se una chiamata ricorsiva restituisce un teorema 504che uguaglia l'input a falso, c'� bisogno di fare di pi�. Se questa � la 505prima chiamata, allora � necessario controllare l'altro ramo. Se anche questo 506restituisce che il teorema � insoddisfacibile, allora abbiamo due teoremi. 507\[ 508\phi_0,\Delta,(v\!=\!\holtxt{T})\vdash \holtxt{F} \qquad 509\phi_0,\Delta,(v\!=\!\holtxt{F})\vdash \holtxt{F} 510\] dove $\phi_0$ � la formula originaria, $\Delta$ � il resto 511dell'assegnazione corrente, e $v$ � la variabile su cui � stato appena eseguito 512uno split. Per trasformare questi due teoremi nel desiderato 513\[ 514\phi_0,\Delta\vdash \holtxt{F} 515\] 516useremo la regola d'inferenza \ml{DISJ\_CASES}: 517\[ 518\infer{\Gamma \cup \Delta_1 \cup \Delta_2 \vdash \phi}{ 519 \Gamma \vdash \psi \lor \xi & 520 \Delta_1 \cup \{\psi\}\vdash \phi & 521 \Delta_2 \cup \{\xi\} \vdash \phi 522} 523\] 524e il teorema \ml{BOOL\_CASES\_AX}: 525\[ 526\vdash \forall t.\;(t = \holtxt{T}) \lor (t = \holtxt{F}) 527\] 528 529Possiamo mettere insieme questi frammenti e scrivere la funzione \ml{CoreDPLL} 530di alto livello, nella Figura~\ref{fig:coredpll}. 531\begin{figure}[htbp] 532\begin{holboxed} 533\begin{verbatim} 534fun CoreDPLL form = let 535 val initial_th = ASSUME form 536 fun recurse th = let 537 val c = concl th 538 in 539 if c = boolSyntax.T then 540 mk_satmap th 541 else if c = boolSyntax.F then 542 Unsat th 543 else let 544 val v = find_splitting_var c 545 val (l,r) = casesplit v th 546 in 547 case recurse l of 548 Unsat l_false => let 549 in 550 case recurse r of 551 Unsat r_false => 552 Unsat (DISJ_CASES (SPEC v BOOL_CASES_AX) l_false r_false) 553 | x => x 554 end 555 | x => x 556 end 557 end 558in 559 case (recurse initial_th) of 560 Unsat th => Unsat (CONV_RULE (REWR_CONV IMP_F_EQ_F) (DISCH form th)) 561 | x => x 562end 563\end{verbatim} 564\end{holboxed} 565\caption{Il nucleo della funzione DPLL} 566\label{fig:coredpll} 567\end{figure} 568 569 570Tutto ci� che rimane da fare � capire su quale variabile effettuare 571il case split. Le variabili pi� importanti su cui effettuare lo split sono quelle 572che appaiono in quelle che sono chiamate ``unit clauses'', clausole che contengono 573solo un letterale. Se c'� una unit clause in una formula allora � 574della forma 575\[ 576\phi \land v \land \phi' 577\] 578o 579\[ 580\phi \land \neg v \land \phi' 581\] 582In entrambe le situazioni, effettuare lo split su $v$ risulter� sempre in un ramo 583che valuta direttamente a falso. Cos� eliminiamo una variabile 584senza accrescere la dimensione del problema. Il processo di 585eliminare clausole unitarie � di solito chiamato ``unit propagation''. 586La unit propagation di solito non � pensata come un'operazione 587di case splitting, ma eseguirla in questo modo render� il nostro codice pi� semplice. 588 589Se una formula non include una clausola unitaria, allora la scelta della prossima 590variabile su cui eseguire lo split � molto pi� di una magia nera. Qui 591implementeremo una scelta molto semplice: per eseguire lo split sulla variabile che occorre 592pi� di frequente. La nostra funzione \ml{find\_splitting\_var} prende una formula 593e restituisce la variabile su cui eseguire lo split. 594\begin{hol} 595\begin{verbatim} 596 fun find_splitting_var phi = let 597 fun recurse acc [] = getBiggest acc 598 | recurse acc (c::cs) = let 599 val ds = strip_disj c 600 in 601 case ds of 602 [lit] => (dest_neg lit handle HOL_ERR _ => lit) 603 | _ => recurse (count_vars ds acc) cs 604 end 605 in 606 recurse (Binarymap.mkDict Term.compare) (strip_conj phi) 607 end 608\end{verbatim} 609\end{hol} 610Questa funzione lavora passando una lista di clausole alla funzione 611pi� interna \ml{recurse}. Questa spoglia ciascuna clausola una alla volta. Se una 612clausola ha un solo disgiunto � una clausola unitaria e la variabile pu� 613essere restituita direttamente. Altrimenti, le variabili nelle clausole sono 614contate e aggiunte da \ml{count\_vars} alla map che si accumula, e la 615ricorsione pu� continuare. 616 617La funzione \ml{count\_vars} ha la seguente implementazione: 618\begin{hol} 619\begin{verbatim} 620 fun count_vars ds acc = 621 case ds of 622 [] => acc 623 | lit::lits => let 624 val v = dest_neg lit handle HOL_ERR _ => lit 625 in 626 case Binarymap.peek (acc, v) of 627 NONE => count_vars lits (Binarymap.insert(acc,v,1)) 628 | SOME n => count_vars lits (Binarymap.insert(acc,v,n + 1)) 629 end 630\end{verbatim} 631\end{hol} 632 633L'uso di un albero binario per archiviare dati variabili rende efficiente 634aggiornare i dati mentre sono raccolti. Estrarre la variabile 635con il conteggio pi� grande � poi una scansione lineare dell'albero, che possiamo 636fare con la funzione \ml{foldl}: 637\begin{hol} 638\begin{verbatim} 639 fun getBiggest acc = 640 #1 (Binarymap.foldl(fn (v,cnt,a as (bestv,bestcnt)) => 641 if cnt > bestcnt then (v,cnt) else a) 642 (boolSyntax.T, 0) 643 acc 644\end{verbatim} 645\end{hol} 646 647\subsection{Performance} 648\label{sec:dpll-performance} 649 650Quando vengono dati input un p� oltre quelli chiaramente banali, la funzione 651che abbiamo scritto (al livello alto, \ml{DPLL\_UNIV}) ha delle performance considerevolmente 652migliori rispetto all'implementazione con le tavole di verit�. Per esempio, la 653generalizzazione del seguente termine, con 29 variabili, richiede a 654\ml{wrapper} tre secondi e mezzo per essere dimostrata come tautologia: 655\begin{hol} 656\begin{verbatim} 657 (s0_0 = (x_0 = ~y_0)) /\ (c0_1 = x_0 /\ y_0) /\ 658 (s0_1 = ((x_1 = ~y_1) = ~c0_1)) /\ 659 (c0_2 = x_1 /\ y_1 \/ (x_1 \/ y_1) /\ c0_1) /\ 660 (s0_2 = ((x_2 = ~y_2) = ~c0_2)) /\ 661 (c0_3 = x_2 /\ y_2 \/ (x_2 \/ y_2) /\ c0_2) /\ 662 (s1_0 = ~(x_0 = ~y_0)) /\ (c1_1 = x_0 /\ y_0 \/ x_0 \/ y_0) /\ 663 (s1_1 = ((x_1 = ~y_1) = ~c1_1)) /\ 664 (c1_2 = x_1 /\ y_1 \/ (x_1 \/ y_1) /\ c1_1) /\ 665 (s1_2 = ((x_2 = ~y_2) = ~c1_2)) /\ 666 (c1_3 = x_2 /\ y_2 \/ (x_2 \/ y_2) /\ c1_2) /\ 667 (c_3 = ~c_0 /\ c0_3 \/ c_0 /\ c1_3) /\ 668 (s_0 = ~c_0 /\ s0_0 \/ c_0 /\ s1_0) /\ 669 (s_1 = ~c_0 /\ s0_1 \/ c_0 /\ s1_1) /\ 670 (s_2 = ~c_0 /\ s0_2 \/ c_0 /\ s1_2) /\ ~c_0 /\ 671 (s2_0 = (x_0 = ~y_0)) /\ (c2_1 = x_0 /\ y_0) /\ 672 (s2_1 = ((x_1 = ~y_1) = ~c2_1)) /\ 673 (c2_2 = x_1 /\ y_1 \/ (x_1 \/ y_1) /\ c2_1) /\ 674 (s2_2 = ((x_2 = ~y_2) = ~c2_2)) /\ 675 (c2_3 = x_2 /\ y_2 \/ (x_2 \/ y_2) /\ c2_2) ==> 676 (c_3 = c2_3) /\ (s_0 = s2_0) /\ (s_1 = s2_1) /\ (s_2 = s2_2) 677\end{verbatim} 678\end{hol} 679(Se si desidera la velocit� reale, la funzione incorporata \ml{tautLib.TAUT\_PROVE} esegue ci� che sta sopra in meno di un centesimo di secondo, usando uno strumento esterno per generare la dimostrazione di insoddisfacibilit�, e per poi tradurre quella dimostrazione indietro in HOL.) 680 681\section{Estendere l'Applicabilit� della nostra Procedura} 682\label{sec:dpll-applicability-extension} 683 684La funzione \ml{DPLL\_UNIV} richiede che il suo input sia quantificato 685universalmente, con tutte le variabili libere legate, e che ciascun letterale sia 686una variabile o la negazione di una variabile. Questo rende \ml{DPLL\_UNIV} 687poco facile da usare quando viene ad essere usata come parte della dimostrazione di 688un goal. In questa sezione, scriveremo un ulteriore livello 689``wrapper'' per avvolgere \ml{DPLL\_UNIV}, producendo uno strumento che pu� 690essere applicato a molti pi� goal. 691 692\paragraph{Mitigare il Requisito di Quantificazione} Il primo passo � 693permettere formule che non sono chiuse. Al fine di trasmettere una formula 694che \emph{�} chiusa a \ml{DPLL\_UNIV}, possiamo semplicemente generalizzare 695sulle variabili libere della formula. Se \ml{DPLL\_UNIV} dice poi che 696la nuova, formula ground � vera, allora lo sar� anche l'originale. Dall'altro 697lato, se \ml{DPLL\_UNIV} dice che la formula ground � 698falsa, allora non possiamo concludere niente di pi� e dovremo sollevare 699un'eccezione. 700 701Il codice che implementa questo � mostrato di sotto: 702\begin{hol} 703\begin{verbatim} 704 fun nonuniv_wrap t = let 705 val fvs = free_vars t 706 val gen_t = list_mk_forall(fvs, t) 707 val gen_t_eq = DPLL_UNIV gen_t 708 in 709 if rhs (concl gen_t_eq) = boolSyntax.T then let 710 val gen_th = EQT_ELIM gen_t_eq 711 in 712 EQT_INTRO (SPECL fvs gen_th) 713 end 714 else 715 raise mk_HOL_ERR "dpll" "nonuniv_wrap" "No conclusion" 716 end 717\end{verbatim} 718\end{hol} 719 720\paragraph{Permettere Foglie Non Letterali} 721Possiamo fare meglio di \ml{nonuniv\_wrap}: piuttosto che quantificare solamente 722solo sulle variabili libere (che abbiamo convenientemente assunto che saranno solo 723booleane), possiamo trasformare ogni parte foglia del termine che non � una 724variabile o la negazione di una variabile in una nuova variabile. Prima 725estraiamo quelle foglie con valore booleano che non sono le costanti vero o 726falso. 727\begin{hol} 728\begin{verbatim} 729 fun var_leaves acc t = let 730 val (l,r) = dest_conj t handle HOL_ERR _ => 731 dest_disj t handle HOL_ERR _ => 732 dest_imp t handle HOL_ERR _ => 733 dest_bool_eq t 734 in 735 var_leaves (var_leaves acc l) r 736 end handle HOL_ERR _ => 737 if type_of t <> bool then 738 raise mk_HOL_ERR "dpll" "var_leaves" "Term not boolean" 739 else if t = boolSyntax.T then acc 740 else if t = boolSyntax.F then acc 741 else HOLset.add(acc, t) 742\end{verbatim} 743\end{hol} 744Si noti che non abbiamo tentato esplicitamente di separare le negazioni 745booleane (il che si potrebbe fare con \ml{dest\_neg}). Questo perch� 746anche \ml{dest\_imp} distrugge termini \holtxt{\~{}p}, restituendo 747\holtxt{p} e \holtxt{F} come l'antecedente e la conclusione. Abbiamo 748anche usato una funzione \ml{dest\_bool\_eq} progettata per dividere solo 749quelle uguaglianze che sono su valori booleani. La sua definizione � 750\begin{hol} 751\begin{verbatim} 752 fun dest_bool_eq t = let 753 val (l,r) = dest_eq t 754 val _ = type_of l = bool orelse 755 raise mk_HOL_ERR "dpll" "dest_bool_eq" "Eq not on bools" 756 in 757 (l,r) 758 end 759\end{verbatim} 760\end{hol} 761 762Ora possiamo scrivere la nostra funzione finale \ml{DPLL\_TAUT}: 763\begin{hol} 764\begin{verbatim} 765 fun DPLL_TAUT tm = 766 let val (univs,tm') = strip_forall tm 767 val insts = HOLset.listItems (var_leaves empty_tmset tm') 768 val vars = map (fn t => genvar bool) insts 769 val theta = map2 (curry (op |->)) insts vars 770 val tm'' = list_mk_forall (vars,subst theta tm') 771 in 772 EQT_INTRO (GENL univs 773 (SPECL insts (EQT_ELIM (DPLL_UNIV tm'')))) 774 end 775\end{verbatim} 776\end{hol} 777Si noti come questo codice prima tira fuori tutte le quantificazioni universali 778esterne (con \ml{strip\_forall}), e poi ri-generalizza 779(con \ml{list\_mk\_forall}). Le chiamate a \ml{GENL} e \ml{SPECL} 780annullano queste manipolazioni, ma al livello dei teoremi. Questo produce 781un teorema che uguaglia l'input originale a vero. (Se il termine di input non 782� un'istanza di una formula proposizionale valida, la chiamata a 783\ml{EQT\_ELIM} sollever� un'eccezione.) 784 785\section*{Esercizi} 786 787\begin{enumerate} 788\item Estendere la procedura cos� che gestisca le espressioni condizionali 789 (entrambi i rami dei termini devono essere di tipo booleano). 790\end{enumerate} 791 792 793%%% Local Variables: 794%%% mode: latex 795%%% TeX-master: "tutorial" 796%%% End: 797