1(* Copyright (c) 2010 Tjark Weber. All rights reserved. *) 2 3(* QBF certificates and proof reconstruction. 4 5 As defined in "A File Format for QBF Certificates" by Daniel Kroening and 6 Christoph M. Wintersteiger (2007-05-01, available at 7 http://www.cprover.org/qbv/download/qbcformat.pdf). 8 9 Also see "A First Step Towards a Unified Proof Checker for QBF" by Toni 10 Jussila, Armin Biere, Carsten Sinz, Daniel Kr��ning and Christoph (UOK) 11 Wintersteiger, published at SAT 2007 (vol. 4501 of LNCS). 12 13 We additionally require that certificates of validity contain "extensions" 14 only, and that certificates of invalidity contain "resolutions" only (as 15 defined in the first paper above). 16*) 17 18structure QbfCertificate = 19struct 20 21 val ERR = Feedback.mk_HOL_ERR "QbfCertificate" 22 23(* ------------------------------------------------------------------------- *) 24(* The type of QBF certificates. *) 25(* ------------------------------------------------------------------------- *) 26 27 type cindex = int (* clause index, must be >= 1 *) 28 type vindex = int (* variable index, must be >= 1 *) 29 type literal = int (* a possibly negated variable index, must be <> 0 *) 30 31 datatype extension = ITE of literal * literal * literal 32 | AND of literal list 33 34 type resolution = literal list * cindex list 35 36 datatype certificate = 37 VALID of (vindex, extension) Redblackmap.dict 38 * (vindex, literal) Redblackmap.dict 39 | INVALID of (cindex, resolution) Redblackmap.dict * cindex 40 41(* ------------------------------------------------------------------------- *) 42(* read_certificate_file: reads a QBF certificate from a file *) 43(* ------------------------------------------------------------------------- *) 44 45 (* This would arguably be much nicer to implement with parser combinators. 46 Or perhaps one should use mllex/mlyacc. *) 47 48 fun read_certificate_file path = 49 let 50 (* string list -> string list *) 51 fun filter_header ("QBCertificate\n" :: lines) = 52 lines 53 | filter_header _ = 54 raise ERR "read_certificate_file" "'QBCertificate' header not found" 55 (* string -> int *) 56 fun int_from_string s = 57 case Int.fromString s of 58 NONE => 59 raise ERR "read_certificate_file" 60 ("integer expected ('" ^ s ^ "' found)") 61 | SOME i => 62 i 63 (* literal list -> string list -> literal list *) 64 fun extension_lits lits ["0"] = 65 List.rev lits 66 | extension_lits _ ("0" :: _) = 67 raise ERR "read_certificate_file" 68 "unexpected input after '0'-terminated list of extension literals" 69 | extension_lits _ [] = 70 raise ERR "read_certificate_file" 71 "missing '0' terminator after extension literals" 72 | extension_lits lits (literal :: xs) = 73 extension_lits (int_from_string literal :: lits) xs 74 (* (vindex, extension) dict -> string list -> (vindex, extension) dict *) 75 fun extension ext [vindex, "I", lit1, lit2, lit3] = 76 Redblackmap.insert (ext, int_from_string vindex, 77 ITE (int_from_string lit1, int_from_string lit2, int_from_string lit3)) 78 | extension ext (vindex :: "A" :: lits) = 79 Redblackmap.insert (ext, int_from_string vindex, 80 AND (extension_lits [] lits)) 81 | extension _ _ = 82 raise ERR "read_certificate_file" "extension: invalid format" 83 (* cindex list -> string list -> cindex list *) 84 fun resolution_clauses clauses ["0"] = 85 List.rev clauses 86 | resolution_clauses clauses ("0" :: _) = 87 raise ERR "read_certificate_file" 88 "unexpected input after '0'-terminated list of clauses" 89 | resolution_clauses clauses (cindex :: xs) = 90 resolution_clauses (int_from_string cindex :: clauses) xs 91 | resolution_clauses _ [] = 92 raise ERR "read_certificate_file" 93 "resolution: '0' expected to terminate list of clauses" 94 (* literal list -> string list -> resolution *) 95 fun resolution_args [] ("*" :: xs) = 96 ([], resolution_clauses [] xs) 97 | resolution_args _ ("*" :: _) = 98 raise ERR "read_certificate_file" 99 "resolution: '*' found after list of literals (use '0' instead)" 100 | resolution_args lits ("0" :: xs) = 101 (List.rev lits, resolution_clauses [] xs) 102 | resolution_args lits (lit :: xs) = 103 resolution_args (int_from_string lit :: lits) xs 104 | resolution_args _ [] = 105 raise ERR "read_certificate_file" 106 "resolution: missing '*' or '0' terminator" 107 (* (cindex, resolution) dict -> string list -> (cindex, resolution) dict *) 108 fun resolution res (cindex :: xs) = 109 Redblackmap.insert (res, int_from_string cindex, resolution_args [] xs) 110 | resolution _ _ = 111 raise ERR "read_certificate_file" "resolution: clause index expected" 112 (* (vindex, literal) dict -> string list -> (vindex, literal) dict *) 113 fun valid_conclusion dict [] = 114 dict 115 | valid_conclusion dict (vindex :: literal :: xs) = 116 valid_conclusion (Redblackmap.insert 117 (dict, int_from_string vindex, int_from_string literal)) xs 118 | valid_conclusion _ _ = 119 raise ERR "read_certificate_file" 120 "vindex/literal pair expected in conclusion" 121 (* (vindex, extension) dict * (cindex, resolution) dict -> string list -> 122 conclusion *) 123 fun conclusion (ext, res) ("VALID" :: xs) = 124 let 125 val _ = Redblackmap.isEmpty res orelse 126 raise ERR "read_certificate_file" 127 "conclusion is 'VALID', but there are resolutions" 128 in 129 VALID (ext, valid_conclusion (Redblackmap.mkDict Int.compare) xs) 130 end 131 | conclusion (ext, res) ["INVALID", cindex] = 132 let 133 val _ = Redblackmap.isEmpty ext orelse 134 raise ERR "read_certificate_file" 135 "conclusion is 'INVALID', but there are extensions" 136 in 137 INVALID (res, int_from_string cindex) 138 end 139 | conclusion _ _ = 140 raise ERR "read_certificate_file" "conclusion: invalid format" 141 (* (vindex, extension) dict * (cindex, resolution) dict -> string list list 142 -> certificate *) 143 fun certificate (ext, res) ["CONCLUDE" :: xs] = 144 conclusion (ext, res) xs 145 | certificate _ (("CONCLUDE" :: _) :: _) = 146 raise ERR "read_certificate_file" "unexpected input after conclusion" 147 | certificate (ext, res) (("E" :: xs) :: xss) = 148 certificate (extension ext xs, res) xss 149 | certificate (ext, res) (xs :: xss) = 150 certificate (ext, resolution res xs) xss 151 | certificate _ [] = 152 raise ERR "read_certificate_file" "missing conclusion" 153 in 154 (certificate 155 (Redblackmap.mkDict Int.compare, Redblackmap.mkDict Int.compare) 156 o (map (String.tokens (Lib.C Lib.mem [#" ", #"\t", #"\n"]))) 157 o filter_header 158 o List.filter (fn l => l <> "\n") 159 o QbfLibrary.read_lines_from_file) path 160 end 161 162(* ------------------------------------------------------------------------- *) 163(* check: proves or disproves the QBF 't' (see QDimacs.sml for the format of *) 164(* QBFs), using an appropriate certificate. Returns either "|- t" (if *) 165(* the certificate is 'VALID ...') or "t |- F" (if the certificate is *) 166(* 'INVALID ...'). Fails if 't' is not a QBF, or if the certificate is *) 167(* not suitable for 't'. *) 168(* ------------------------------------------------------------------------- *) 169 170 local open Term in 171 datatype vtype = Forall of term (* var *) 172 | Exists of (term * term) (* var, extvar *) 173 | Ext of (term * term) (* extvar, definition *) 174 end 175 176 val sat_prove = ref HolSatLib.SAT_PROVE 177 178 fun check t dict (VALID (exts,lits)) = let 179 open Lib Thm Drule Term Type boolSyntax 180 open Redblackset Redblackmap 181 182 val (var_to_index, index_to_var) = let 183 open String Int Option 184 val s = "v" (*TODO*) 185 fun num_to_var n = mk_var(s^(toString n),bool) 186 (* in this case we use the inverse of dict to 187 map indexes to variables, but since dict only 188 binds original variables, we update the inverse map 189 on indexes of extension variables as necessary, 190 (using num_to_var for extensions) *) 191 fun invert_dict d = 192 foldl (fn(v,n,d)=>insert(d,n,v)) (mkDict compare) d 193 val tcid = ref (invert_dict dict) 194 fun update (n,v) = (tcid := insert(!tcid,n,v); v) 195 in 196 (curry find dict, 197 fn n => find (!tcid,n) 198 handle NotFound => update (n,num_to_var n)) 199 end 200 201 (* Strip quantifiers from t and return the matrix mat. 202 Update lits map to map any unmapped variables to 0 (meaning T). 203 Create vars map from a variable index to a vtype. 204 Existential variables without witnesses are given an rhs of T. 205 Create deps map from a variable index to a list of indexes 206 indicating that this variable will appear on the rhs 207 of the hypothesis defining each variable in the list. 208 Deps also has the variable x_{n+1}, bound immediately 209 after (inside) a variable x_n, in the list for x_n. 210 After this pass, deps will map extension variables that are witnesses 211 to singleton lists containing their corresponding existential variables. 212 It will also map existential and universal variables to singleton 213 lists containing the next bound variable (empty list for innermost) *) 214 val (vars,mat,lits,deps,lvi) = let 215 val cmp = Int.compare 216 fun enum vars t lits' deps lvi = let 217 val ((v,t), is_forall) = (dest_forall t, true) 218 handle Feedback.HOL_ERR _ => (dest_exists t, false) 219 val vi = var_to_index v 220 val (var,lits',deps) = 221 if is_forall then (Forall v,lits',deps) else let 222 val (tm,lits',deps) = let 223 val ext_lit = find(lits,vi) 224 val ext_index = Int.abs ext_lit 225 val tm = index_to_var ext_index 226 val tm = if ext_lit < 0 then mk_neg tm else tm 227 in (tm,lits',insert(deps,ext_index,[vi])) end 228 handle NotFound => (T,insert(lits',vi,0),deps) 229 in (Exists (v,tm),lits',deps) end 230 val deps = case lvi of NONE => deps | SOME lvi => insert(deps,lvi,[vi]) 231 in enum (insert(vars,vi,var)) t lits' deps (SOME vi) end 232 handle Feedback.HOL_ERR _ => (vars,t,lits',deps,lvi) 233 in enum (mkDict Int.compare) t lits (mkDict Int.compare) NONE end 234 val deps = case lvi of NONE => deps | SOME lvi => insert(deps,lvi,[]) 235 236 (* add all the hypotheses for the original existential variables 237 onto the front of the matrix, so we end up with 238 (v1 = e1) ==> (v2 = e2) ==> ... ==> mat *) 239 fun foldthis (_,Forall _,t) = t 240 | foldthis (_,Ext _,t) = t 241 | foldthis (_,Exists (v,tm),t) = mk_imp(mk_eq(v,tm),t) 242 val mat = (foldl foldthis mat) vars 243 244 (* extension_to_term calculates a term corresponding 245 to the definition of an extension variable, 246 plus the set of indexes that term depends on. 247 If an extension is defined using an original existential variable v, 248 replace references to v by references to v's witness (extension) variable. 249 If v has no witness, replace references to v by references to T, 250 but simplify as necessary. 251 For example, if v has no witness: 252 if v occurs in an AND, don't bother listing it. 253 if ~v occurs in an AND, replace the AND by F, 254 if v is the test in an ITE, replace the ITE by its consequent 255 etc. *) 256 local 257 val empty = empty Int.compare 258 (* lit processes a literal l, accumulating dependencies in s. 259 TFk is the continuation for when l has no witness. 260 TFk is passed whether l was not negated 261 (i.e. will be constant T, rather than constant F) 262 vk is the continuation for when l has a witness. 263 vk is passed the literal of the witness, 264 and s with the index of the witness added *) 265 fun lit (l,s) TFk vk = let 266 val index = Int.abs l 267 in let 268 val el = find(lits,index) 269 in if el = 0 then TFk (l > 0) else let 270 val ext_index = Int.abs el 271 val s = add(s,ext_index) 272 val v = index_to_var ext_index 273 val neg = if l < 0 then el > 0 else el < 0 274 val v = if neg then mk_neg v else v 275 in vk (v,s) end end 276 handle NotFound => let 277 val s = add(s,index) 278 val v = index_to_var index 279 val v = if l < 0 then mk_neg v else v 280 in vk (v,s) end end 281 exception False 282 fun afold (l,(t,s)) = lit (l,s) 283 (fn true=>(t,s)|false=>raise False) 284 (fn(v,s)=> 285 (case t of NONE => SOME v 286 | SOME t => SOME (mk_conj(v,t)), s)) 287 fun negate t = 288 dest_neg t handle Feedback.HOL_ERR _ => mk_neg t 289 in 290 fun extension_to_term (AND ls) = 291 (let 292 val (t,s) = List.foldl afold (NONE,empty) ls 293 in case t of NONE => (T,s) 294 | SOME t => (t,s) 295 end handle False => (F,empty)) 296 | extension_to_term (ITE(t,c,a)) = 297 lit (t,empty) 298 (fn t=> lit (if t then c else a,empty) 299 (fn true=>(T,empty)|false=>(F,empty)) 300 (fn(v,s)=>(v,s))) 301 (fn(t,s)=> lit (c,s) 302 (fn c=> lit (a,s) 303 (fn a=>(if c then if a then T else t 304 else if a then negate t else F, 305 s)) 306 (fn(a,s)=>(if c then mk_disj(t,a) 307 else mk_conj(negate t,a),s))) 308 (fn(c,s)=> lit (a,s) 309 (fn a=>((if a then mk_imp else mk_conj)(t,c),s)) 310 (fn(a,s)=>(mk_cond(t,c,a),s)))) 311 end 312 313 (* Compute the terms for each existential variable 314 and add (e = tm) hypotheses onto the matrix. 315 Fill in the rest of the vars map. 316 Fill in the deps map by adding each extension variable 317 to the list belonging to each variable appearing in its definition term. *) 318 fun foldthis (i,ext,(t,vars,deps)) = let 319 val v = index_to_var i 320 val (tm,ds) = extension_to_term ext 321 val vars = insert(vars,i,Ext(v,tm)) 322 val h = mk_eq(v,tm) 323 fun foldthis (n,d) = update (d,n,fn NONE => [i] | SOME ls => i::ls) 324 val deps = Redblackset.foldl foldthis deps ds 325 in (mk_imp(h,t),vars,deps) end 326 val (mat,vars,deps) = (foldl foldthis (mat,vars,deps)) exts 327 328 val thm = (!sat_prove) mat 329 330 val deps = Lib.dict_topsort deps 331 val deps = List.rev deps 332 (* now deps is the list of variable indexes in the order 333 they should be eliminated (quantified and have hypothesis discharged) *) 334 335 fun foldthis (i,th) = case find(vars,i) of 336 Forall v => (fn() => GEN v th) () 337 | Exists (v,w) => (fn () => let 338 val th = EXISTS (mk_exists(v,concl th),v) th 339 val ex = EXISTS (mk_exists(v,mk_eq(v,w)),w) (REFL w) 340 val th = CHOOSE (v,ex) th 341 in th end ) () 342 | Ext (v,w) => (fn () => let 343 val ex = EXISTS (mk_exists(v,mk_eq(v,w)),w) (REFL w) 344 val th = CHOOSE (v,ex) th 345 in th end ) () 346 val thm = DISCH_ALL (List.foldl foldthis (UNDISCH_ALL thm) deps) 347 348 (* sanity checks *) 349 val _ = if !QbfTrace.trace < 1 orelse HOLset.isEmpty (Thm.hypset thm) then 350 () 351 else 352 Feedback.HOL_WARNING "QbfCertificate" "check" "final theorem has hyps" 353 val _ = if !QbfTrace.trace < 1 orelse Term.aconv (Thm.concl thm) t then 354 () 355 else 356 Feedback.HOL_WARNING "QbfCertificate" "check" 357 "final theorem proves a different term" 358 in 359 thm 360 end 361(* ------------------------------------------------------------------------- *) 362 | check t _ (INVALID (dict, cindex)) = 363 let 364 (* pre-processing: break the problem apart into clauses in sequent form 365 suitable for Q-resolution *) 366 367 (* We assume that there are no free variables in 't', so that *all* 368 variables in the matrix occur in 'vars'. *) 369 val (_, vars, matrix) = QbfLibrary.enumerate_quantified_vars t 370 371 (* a dictionary that maps each variable to a pair, which consists of the 372 variable's index and a Boolean that is true if the variable is 373 universally quantified, and false if it is existentially quantified *) 374 val var_dict = List.foldl (fn ((i, var, is_forall), var_dict) => 375 Redblackmap.insert (var_dict, var, (i, is_forall))) 376 (Redblackmap.mkDict Term.var_compare) vars 377 fun index_fn var = 378 Redblackmap.find (var_dict, var) 379 380 val vars = List.rev vars 381 fun foldthis (clause, (i, clause_dict)) = 382 let 383 val clause = QbfLibrary.CLAUSE_TO_SEQUENT clause 384 val lits = QbfLibrary.literals_in_clause index_fn clause 385 in 386 (i + 1, Redblackmap.insert (clause_dict, i, 387 QbfLibrary.forall_reduce (clause, vars, matrix, lits))) 388 end 389 390 (* a dictionary that maps each clause identifier to a 4-tuple, which 391 consists of 1. the clause theorem (in sequent form, cf. 392 'QbfLibrary.CLAUSE_TO_SEQUENT'), 2. the list of missing variables (cf. 393 'QbfLibrary.enumerate_quantified_vars', 3. the hypothesis (initially, 394 this is 'matrix'), and 4. the list of literals in the clause (cf. 395 'QbfLibrary.literals_in_clause' *) 396 val clause_dict = Lib.snd (List.foldl foldthis 397 (1, Redblackmap.mkDict Int.compare) 398 (Drule.CONJUNCTS (Thm.ASSUME matrix))) 399 400 (* depth-first recursion over the certificate (which represents a DAG), 401 using QRESOLVE_CLAUSES to derive new clauses from existing ones *) 402 fun derive (c_dict, index) = 403 case Redblackmap.peek (c_dict, index) of 404 SOME clause => 405 (c_dict, clause) 406 | NONE => 407 let 408 val (_, indices) = Redblackmap.find (dict, index) 409 handle Redblackmap.NotFound => 410 raise ERR "check" 411 ("invalid certificate: no definition for clause ID " ^ 412 Int.toString index) 413 val _ = if List.null indices then 414 raise ERR "check" 415 ("invalid certificate: empty definition for clause ID " ^ 416 Int.toString index) 417 else () 418 val (c_dict, clauses) = Lib.foldl_map derive (c_dict, indices) 419 val clause = List.foldl (QbfLibrary.QRESOLVE_CLAUSES) 420 (List.hd clauses) (List.tl clauses) 421 in 422 (Redblackmap.insert (c_dict, index, clause), clause) 423 end 424 425 (* derive "t |- F", using the certificate *) 426 val thm = #1 (Lib.snd (derive (clause_dict, cindex))) 427 428 (* sanity checks *) 429 val _ = if !QbfTrace.trace < 1 orelse 430 (HOLset.numItems (Thm.hypset thm) = 1 andalso 431 HOLset.member (Thm.hypset thm, t)) then 432 () 433 else 434 Feedback.HOL_WARNING "QbfCertificate" "check" "final theorem has hyps" 435 val _ = if !QbfTrace.trace < 1 orelse 436 Term.aconv (Thm.concl thm) boolSyntax.F then 437 () 438 else 439 Feedback.HOL_WARNING "QbfCertificate" "check" "final theorem not F" 440 in 441 thm 442 end 443end 444