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