1(* Title: Provers/blast.ML 2 Author: Lawrence C Paulson, Cambridge University Computer Laboratory 3 Copyright 1997 University of Cambridge 4 5Generic tableau prover with proof reconstruction 6 7 SKOLEMIZES ReplaceI WRONGLY: allow new vars in prems, or forbid such rules?? 8 Needs explicit instantiation of assumptions? 9 10Given the typeargs system, constructor Const could be eliminated, with 11TConst replaced by a constructor that takes the typargs list as an argument. 12However, Const is heavily used for logical connectives. 13 14Blast_tac is often more powerful than fast_tac, but has some limitations. 15Blast_tac... 16 * ignores wrappers (addss, addbefore, addafter, addWrapper, ...); 17 this restriction is intrinsic 18 * ignores elimination rules that don't have the correct format 19 (conclusion must be a formula variable) 20 * rules must not require higher-order unification, e.g. apply_type in ZF 21 + message "Function unknown's argument not a bound variable" relates to this 22 * its proof strategy is more general but can actually be slower 23 24Known problems: 25 "Recursive" chains of rules can sometimes exclude other unsafe formulae 26 from expansion. This happens because newly-created formulae always 27 have priority over existing ones. But obviously recursive rules 28 such as transitivity are treated specially to prevent this. Sometimes 29 the formulae get into the wrong order (see WRONG below). 30 31 With substition for equalities (hyp_subst_tac): 32 When substitution affects an unsafe formula or literal, it is moved 33 back to the list of safe formulae. 34 But there's no way of putting it in the right place. A "moved" or 35 "no DETERM" flag would prevent proofs failing here. 36*) 37 38signature BLAST_DATA = 39sig 40 structure Classical: CLASSICAL 41 val Trueprop_const: string * typ 42 val equality_name: string 43 val not_name: string 44 val notE: thm (* [| ~P; P |] ==> R *) 45 val ccontr: thm 46 val hyp_subst_tac: Proof.context -> bool -> int -> tactic 47end; 48 49signature BLAST = 50sig 51 exception TRANS of string (*reports translation errors*) 52 datatype term = 53 Const of string * term list 54 | Skolem of string * term option Unsynchronized.ref list 55 | Free of string 56 | Var of term option Unsynchronized.ref 57 | Bound of int 58 | Abs of string*term 59 | $ of term*term; 60 val depth_tac: Proof.context -> int -> int -> tactic 61 val depth_limit: int Config.T 62 val trace: bool Config.T 63 val stats: bool Config.T 64 val blast_tac: Proof.context -> int -> tactic 65 (*debugging tools*) 66 type branch 67 val tryIt: Proof.context -> int -> string -> 68 {fullTrace: branch list list, 69 result: ((int -> tactic) list * branch list list * (int * int * exn) list)} 70end; 71 72functor Blast(Data: BLAST_DATA): BLAST = 73struct 74 75structure Classical = Data.Classical; 76 77(* options *) 78 79val depth_limit = Attrib.setup_config_int \<^binding>\<open>blast_depth_limit\<close> (K 20); 80val (trace, _) = Attrib.config_bool \<^binding>\<open>blast_trace\<close> (K false); 81val (stats, _) = Attrib.config_bool \<^binding>\<open>blast_stats\<close> (K false); 82 83 84datatype term = 85 Const of string * term list (*typargs constant--as a term!*) 86 | Skolem of string * term option Unsynchronized.ref list 87 | Free of string 88 | Var of term option Unsynchronized.ref 89 | Bound of int 90 | Abs of string*term 91 | op $ of term*term; 92 93(*Pending formulae carry md (may duplicate) flags*) 94type branch = 95 {pairs: ((term*bool) list * (*safe formulae on this level*) 96 (term*bool) list) list, (*unsafe formulae on this level*) 97 lits: term list, (*literals: irreducible formulae*) 98 vars: term option Unsynchronized.ref list, (*variables occurring in branch*) 99 lim: int}; (*resource limit*) 100 101 102(* global state information *) 103 104datatype state = State of 105 {ctxt: Proof.context, 106 names: Name.context Unsynchronized.ref, 107 fullTrace: branch list list Unsynchronized.ref, 108 trail: term option Unsynchronized.ref list Unsynchronized.ref, 109 ntrail: int Unsynchronized.ref, 110 nclosed: int Unsynchronized.ref, 111 ntried: int Unsynchronized.ref} 112 113fun reserved_const thy c = 114 is_some (Sign.const_type thy c) andalso 115 error ("blast: theory contains reserved constant " ^ quote c); 116 117fun initialize ctxt = 118 let 119 val thy = Proof_Context.theory_of ctxt; 120 val _ = reserved_const thy "*Goal*"; 121 val _ = reserved_const thy "*False*"; 122 in 123 State 124 {ctxt = ctxt, 125 names = Unsynchronized.ref (Variable.names_of ctxt), 126 fullTrace = Unsynchronized.ref [], 127 trail = Unsynchronized.ref [], 128 ntrail = Unsynchronized.ref 0, 129 nclosed = Unsynchronized.ref 0, (*number of branches closed during the search*) 130 ntried = Unsynchronized.ref 1} (*number of branches created by splitting (counting from 1)*) 131 end; 132 133fun gensym (State {names, ...}) x = 134 Unsynchronized.change_result names (Name.variant x); 135 136 137(** Basic syntactic operations **) 138 139fun is_Var (Var _) = true 140 | is_Var _ = false; 141 142fun dest_Var (Var x) = x; 143 144fun rand (f$x) = x; 145 146(* maps (f, [t1,...,tn]) to f(t1,...,tn) *) 147val list_comb : term * term list -> term = Library.foldl (op $); 148 149(* maps f(t1,...,tn) to (f, [t1,...,tn]) ; naturally tail-recursive*) 150fun strip_comb u : term * term list = 151 let fun stripc (f$t, ts) = stripc (f, t::ts) 152 | stripc x = x 153 in stripc(u,[]) end; 154 155(* maps f(t1,...,tn) to f , which is never a combination *) 156fun head_of (f$t) = head_of f 157 | head_of u = u; 158 159 160(** Particular constants **) 161 162fun negate P = Const (Data.not_name, []) $ P; 163 164fun isNot (Const (c, _) $ _) = c = Data.not_name 165 | isNot _ = false; 166 167fun mkGoal P = Const ("*Goal*", []) $ P; 168 169fun isGoal (Const ("*Goal*", _) $ _) = true 170 | isGoal _ = false; 171 172val (TruepropC, TruepropT) = Data.Trueprop_const; 173 174fun mk_Trueprop t = Term.$ (Term.Const (TruepropC, TruepropT), t); 175 176fun strip_Trueprop (tm as Const (c, _) $ t) = if c = TruepropC then t else tm 177 | strip_Trueprop tm = tm; 178 179 180 181(*** Dealing with overloaded constants ***) 182 183(*alist is a map from TVar names to Vars. We need to unify the TVars 184 faithfully in order to track overloading*) 185fun fromType alist (Term.Type(a,Ts)) = list_comb (Const (a, []), map (fromType alist) Ts) 186 | fromType alist (Term.TFree(a,_)) = Free a 187 | fromType alist (Term.TVar (ixn,_)) = 188 (case (AList.lookup (op =) (!alist) ixn) of 189 NONE => let val t' = Var (Unsynchronized.ref NONE) 190 in alist := (ixn, t') :: !alist; t' 191 end 192 | SOME v => v) 193 194fun fromConst thy alist (a, T) = 195 Const (a, map (fromType alist) (Sign.const_typargs thy (a, T))); 196 197 198(*Tests whether 2 terms are alpha-convertible; chases instantiations*) 199fun (Const (a, ts)) aconv (Const (b, us)) = a = b andalso aconvs (ts, us) 200 | (Skolem (a,_)) aconv (Skolem (b,_)) = a = b (*arglists must then be equal*) 201 | (Free a) aconv (Free b) = a = b 202 | (Var (Unsynchronized.ref(SOME t))) aconv u = t aconv u 203 | t aconv (Var (Unsynchronized.ref (SOME u))) = t aconv u 204 | (Var v) aconv (Var w) = v=w (*both Vars are un-assigned*) 205 | (Bound i) aconv (Bound j) = i=j 206 | (Abs(_,t)) aconv (Abs(_,u)) = t aconv u 207 | (f$t) aconv (g$u) = (f aconv g) andalso (t aconv u) 208 | _ aconv _ = false 209and aconvs ([], []) = true 210 | aconvs (t :: ts, u :: us) = t aconv u andalso aconvs (ts, us) 211 | aconvs _ = false; 212 213 214fun mem_term (_, []) = false 215 | mem_term (t, t'::ts) = t aconv t' orelse mem_term(t,ts); 216 217fun ins_term(t,ts) = if mem_term(t,ts) then ts else t :: ts; 218 219fun mem_var (v: term option Unsynchronized.ref, []) = false 220 | mem_var (v, v'::vs) = v=v' orelse mem_var(v,vs); 221 222fun ins_var(v,vs) = if mem_var(v,vs) then vs else v :: vs; 223 224 225(** Vars **) 226 227(*Accumulates the Vars in the term, suppressing duplicates*) 228fun add_term_vars (Skolem(a,args), vars) = add_vars_vars(args,vars) 229 | add_term_vars (Var (v as Unsynchronized.ref NONE), vars) = ins_var (v, vars) 230 | add_term_vars (Var (Unsynchronized.ref (SOME u)), vars) = add_term_vars (u, vars) 231 | add_term_vars (Const (_, ts), vars) = add_terms_vars (ts, vars) 232 | add_term_vars (Abs (_, body), vars) = add_term_vars (body, vars) 233 | add_term_vars (f $ t, vars) = add_term_vars (f, add_term_vars (t, vars)) 234 | add_term_vars (_, vars) = vars 235(*Term list version. [The fold functionals are slow]*) 236and add_terms_vars ([], vars) = vars 237 | add_terms_vars (t::ts, vars) = add_terms_vars (ts, add_term_vars(t,vars)) 238(*Var list version.*) 239and add_vars_vars ([], vars) = vars 240 | add_vars_vars (Unsynchronized.ref (SOME u) :: vs, vars) = 241 add_vars_vars (vs, add_term_vars(u,vars)) 242 | add_vars_vars (v::vs, vars) = (*v must be a ref NONE*) 243 add_vars_vars (vs, ins_var (v, vars)); 244 245 246(*Chase assignments in "vars"; return a list of unassigned variables*) 247fun vars_in_vars vars = add_vars_vars(vars,[]); 248 249 250 251(*increment a term's non-local bound variables 252 inc is increment for bound variables 253 lev is level at which a bound variable is considered 'loose'*) 254fun incr_bv (inc, lev, u as Bound i) = if i>=lev then Bound(i+inc) else u 255 | incr_bv (inc, lev, Abs(a,body)) = Abs(a, incr_bv(inc,lev+1,body)) 256 | incr_bv (inc, lev, f$t) = incr_bv(inc,lev,f) $ incr_bv(inc,lev,t) 257 | incr_bv (inc, lev, u) = u; 258 259fun incr_boundvars 0 t = t 260 | incr_boundvars inc t = incr_bv(inc,0,t); 261 262 263(*Accumulate all 'loose' bound vars referring to level 'lev' or beyond. 264 (Bound 0) is loose at level 0 *) 265fun add_loose_bnos (Bound i, lev, js) = if i<lev then js 266 else insert (op =) (i - lev) js 267 | add_loose_bnos (Abs (_,t), lev, js) = add_loose_bnos (t, lev+1, js) 268 | add_loose_bnos (f$t, lev, js) = 269 add_loose_bnos (f, lev, add_loose_bnos (t, lev, js)) 270 | add_loose_bnos (_, _, js) = js; 271 272fun loose_bnos t = add_loose_bnos (t, 0, []); 273 274fun subst_bound (arg, t) : term = 275 let fun subst (t as Bound i, lev) = 276 if i<lev then t (*var is locally bound*) 277 else if i=lev then incr_boundvars lev arg 278 else Bound(i-1) (*loose: change it*) 279 | subst (Abs(a,body), lev) = Abs(a, subst(body,lev+1)) 280 | subst (f$t, lev) = subst(f,lev) $ subst(t,lev) 281 | subst (t,lev) = t 282 in subst (t,0) end; 283 284 285(*Normalize...but not the bodies of ABSTRACTIONS*) 286fun norm t = case t of 287 Skolem (a, args) => Skolem (a, vars_in_vars args) 288 | Const (a, ts) => Const (a, map norm ts) 289 | (Var (Unsynchronized.ref NONE)) => t 290 | (Var (Unsynchronized.ref (SOME u))) => norm u 291 | (f $ u) => (case norm f of 292 Abs(_,body) => norm (subst_bound (u, body)) 293 | nf => nf $ norm u) 294 | _ => t; 295 296 297(*Weak (one-level) normalize for use in unification*) 298fun wkNormAux t = case t of 299 (Var v) => (case !v of 300 SOME u => wkNorm u 301 | NONE => t) 302 | (f $ u) => (case wkNormAux f of 303 Abs(_,body) => wkNorm (subst_bound (u, body)) 304 | nf => nf $ u) 305 | Abs (a,body) => (*eta-contract if possible*) 306 (case wkNormAux body of 307 nb as (f $ t) => 308 if member (op =) (loose_bnos f) 0 orelse wkNorm t <> Bound 0 309 then Abs(a,nb) 310 else wkNorm (incr_boundvars ~1 f) 311 | nb => Abs (a,nb)) 312 | _ => t 313and wkNorm t = case head_of t of 314 Const _ => t 315 | Skolem(a,args) => t 316 | Free _ => t 317 | _ => wkNormAux t; 318 319 320(*Does variable v occur in u? For unification. 321 Dangling bound vars are also forbidden.*) 322fun varOccur v = 323 let fun occL lev [] = false (*same as (exists occ), but faster*) 324 | occL lev (u::us) = occ lev u orelse occL lev us 325 and occ lev (Var w) = 326 v=w orelse 327 (case !w of NONE => false 328 | SOME u => occ lev u) 329 | occ lev (Skolem(_,args)) = occL lev (map Var args) 330 (*ignore Const, since term variables can't occur in types (?) *) 331 | occ lev (Bound i) = lev <= i 332 | occ lev (Abs(_,u)) = occ (lev+1) u 333 | occ lev (f$u) = occ lev u orelse occ lev f 334 | occ lev _ = false; 335 in occ 0 end; 336 337exception UNIFY; 338 339 340(*Restore the trail to some previous state: for backtracking*) 341fun clearTo (State {ntrail, trail, ...}) n = 342 while !ntrail<>n do 343 (hd(!trail) := NONE; 344 trail := tl (!trail); 345 ntrail := !ntrail - 1); 346 347 348(*First-order unification with bound variables. 349 "vars" is a list of variables local to the rule and NOT to be put 350 on the trail (no point in doing so) 351*) 352fun unify state (vars,t,u) = 353 let val State {ntrail, trail, ...} = state 354 val n = !ntrail 355 fun update (t as Var v, u) = 356 if t aconv u then () 357 else if varOccur v u then raise UNIFY 358 else if mem_var(v, vars) then v := SOME u 359 else (*avoid updating Vars in the branch if possible!*) 360 if is_Var u andalso mem_var(dest_Var u, vars) 361 then dest_Var u := SOME t 362 else (v := SOME u; 363 trail := v :: !trail; ntrail := !ntrail + 1) 364 fun unifyAux (t,u) = 365 case (wkNorm t, wkNorm u) of 366 (nt as Var v, nu) => update(nt,nu) 367 | (nu, nt as Var v) => update(nt,nu) 368 | (Const(a,ats), Const(b,bts)) => if a=b then unifysAux(ats,bts) 369 else raise UNIFY 370 | (Abs(_,t'), Abs(_,u')) => unifyAux(t',u') 371 (*NB: can yield unifiers having dangling Bound vars!*) 372 | (f$t', g$u') => (unifyAux(f,g); unifyAux(t',u')) 373 | (nt, nu) => if nt aconv nu then () else raise UNIFY 374 and unifysAux ([], []) = () 375 | unifysAux (t :: ts, u :: us) = (unifyAux (t, u); unifysAux (ts, us)) 376 | unifysAux _ = raise UNIFY; 377 in (unifyAux(t,u); true) handle UNIFY => (clearTo state n; false) 378 end; 379 380 381(*Convert from "real" terms to prototerms; eta-contract. 382 Code is similar to fromSubgoal.*) 383fun fromTerm thy t = 384 let val alistVar = Unsynchronized.ref [] 385 and alistTVar = Unsynchronized.ref [] 386 fun from (Term.Const aT) = fromConst thy alistTVar aT 387 | from (Term.Free (a,_)) = Free a 388 | from (Term.Bound i) = Bound i 389 | from (Term.Var (ixn,T)) = 390 (case (AList.lookup (op =) (!alistVar) ixn) of 391 NONE => let val t' = Var (Unsynchronized.ref NONE) 392 in alistVar := (ixn, t') :: !alistVar; t' 393 end 394 | SOME v => v) 395 | from (Term.Abs (a,_,u)) = 396 (case from u of 397 u' as (f $ Bound 0) => 398 if member (op =) (loose_bnos f) 0 then Abs(a,u') 399 else incr_boundvars ~1 f 400 | u' => Abs(a,u')) 401 | from (Term.$ (f,u)) = from f $ from u 402 in from t end; 403 404(*A debugging function: replaces all Vars by dummy Frees for visual inspection 405 of whether they are distinct. Function revert undoes the assignments.*) 406fun instVars t = 407 let val name = Unsynchronized.ref "a" 408 val updated = Unsynchronized.ref [] 409 fun inst (Const(a,ts)) = List.app inst ts 410 | inst (Var(v as Unsynchronized.ref NONE)) = (updated := v :: (!updated); 411 v := SOME (Free ("?" ^ !name)); 412 name := Symbol.bump_string (!name)) 413 | inst (Abs(a,t)) = inst t 414 | inst (f $ u) = (inst f; inst u) 415 | inst _ = () 416 fun revert() = List.app (fn v => v:=NONE) (!updated) 417 in inst t; revert end; 418 419 420(* A1==>...An==>B goes to [A1,...,An], where B is not an implication *) 421fun strip_imp_prems (Const (\<^const_name>\<open>Pure.imp\<close>, _) $ A $ B) = 422 strip_Trueprop A :: strip_imp_prems B 423 | strip_imp_prems _ = []; 424 425(* A1==>...An==>B goes to B, where B is not an implication *) 426fun strip_imp_concl (Const (\<^const_name>\<open>Pure.imp\<close>, _) $ A $ B) = strip_imp_concl B 427 | strip_imp_concl A = strip_Trueprop A; 428 429 430 431(*** Conversion of Elimination Rules to Tableau Operations ***) 432 433exception ElimBadConcl and ElimBadPrem; 434 435(*The conclusion becomes the goal/negated assumption *False*: delete it! 436 If we don't find it then the premise is ill-formed and could cause 437 PROOF FAILED*) 438fun delete_concl [] = raise ElimBadPrem 439 | delete_concl (P :: Ps) = 440 (case P of 441 Const (c, _) $ Var (Unsynchronized.ref (SOME (Const ("*False*", _)))) => 442 if c = "*Goal*" orelse c = Data.not_name then Ps 443 else P :: delete_concl Ps 444 | _ => P :: delete_concl Ps); 445 446fun skoPrem state vars (Const (\<^const_name>\<open>Pure.all\<close>, _) $ Abs (_, P)) = 447 skoPrem state vars (subst_bound (Skolem (gensym state "S", vars), P)) 448 | skoPrem _ _ P = P; 449 450fun convertPrem t = 451 delete_concl (mkGoal (strip_imp_concl t) :: strip_imp_prems t); 452 453(*Expects elimination rules to have a formula variable as conclusion*) 454fun convertRule state vars t = 455 let val (P::Ps) = strip_imp_prems t 456 val Var v = strip_imp_concl t 457 in v := SOME (Const ("*False*", [])); 458 (P, map (convertPrem o skoPrem state vars) Ps) 459 end 460 handle Bind => raise ElimBadConcl; 461 462 463(*Like dup_elim, but puts the duplicated major premise FIRST*) 464fun rev_dup_elim ctxt th = (th RSN (2, revcut_rl)) |> Thm.assumption (SOME ctxt) 2 |> Seq.hd; 465 466 467(*Rotate the assumptions in all new subgoals for the LIFO discipline*) 468local 469 (*Count new hyps so that they can be rotated*) 470 fun nNewHyps [] = 0 471 | nNewHyps (Const ("*Goal*", _) $ _ :: Ps) = nNewHyps Ps 472 | nNewHyps (P::Ps) = 1 + nNewHyps Ps; 473 474 fun rot_tac [] i st = Seq.single st 475 | rot_tac (0::ks) i st = rot_tac ks (i+1) st 476 | rot_tac (k::ks) i st = rot_tac ks (i+1) (Thm.rotate_rule (~k) i st); 477in 478fun rot_subgoals_tac (rot, rl) = 479 rot_tac (if rot then map nNewHyps rl else []) 480end; 481 482 483fun cond_tracing true msg = tracing (msg ()) 484 | cond_tracing false _ = (); 485 486fun TRACE ctxt rl tac i st = 487 (cond_tracing (Config.get ctxt trace) (fn () => Thm.string_of_thm ctxt rl); tac i st); 488 489(*Resolution/matching tactics: if upd then the proof state may be updated. 490 Matching makes the tactics more deterministic in the presence of Vars.*) 491fun emtac ctxt upd rl = 492 TRACE ctxt rl (if upd then eresolve_tac ctxt [rl] else ematch_tac ctxt [rl]); 493 494fun rmtac ctxt upd rl = 495 TRACE ctxt rl (if upd then resolve_tac ctxt [rl] else match_tac ctxt [rl]); 496 497(*Tableau rule from elimination rule. 498 Flag "upd" says that the inference updated the branch. 499 Flag "dup" requests duplication of the affected formula.*) 500fun fromRule (state as State {ctxt, ...}) vars rl0 = 501 let 502 val thy = Proof_Context.theory_of ctxt 503 val rl = Thm.transfer thy rl0 504 val trl = rl |> Thm.prop_of |> fromTerm thy |> convertRule state vars 505 fun tac (upd, dup,rot) i = 506 emtac ctxt upd (if dup then rev_dup_elim ctxt rl else rl) i THEN 507 rot_subgoals_tac (rot, #2 trl) i 508 in SOME (trl, tac) end 509 handle 510 ElimBadPrem => (*reject: prems don't preserve conclusion*) 511 (if Context_Position.is_visible ctxt then 512 warning ("Ignoring weak elimination rule\n" ^ Thm.string_of_thm ctxt rl0) 513 else (); 514 Option.NONE) 515 | ElimBadConcl => (*ignore: conclusion is not just a variable*) 516 (cond_tracing (Config.get ctxt trace) 517 (fn () => "Ignoring ill-formed elimination rule:\n" ^ 518 "conclusion should be a variable\n" ^ Thm.string_of_thm ctxt rl0); 519 Option.NONE); 520 521 522(*** Conversion of Introduction Rules ***) 523 524fun convertIntrPrem t = mkGoal (strip_imp_concl t) :: strip_imp_prems t; 525 526fun convertIntrRule state vars t = 527 let val Ps = strip_imp_prems t 528 val P = strip_imp_concl t 529 in (mkGoal P, map (convertIntrPrem o skoPrem state vars) Ps) 530 end; 531 532(*Tableau rule from introduction rule. 533 Flag "upd" says that the inference updated the branch. 534 Flag "dup" requests duplication of the affected formula. 535 Since unsafe rules are now delayed, "dup" is always FALSE for 536 introduction rules.*) 537fun fromIntrRule (state as State {ctxt, ...}) vars rl0 = 538 let 539 val thy = Proof_Context.theory_of ctxt 540 val rl = Thm.transfer thy rl0 541 val trl = rl |> Thm.prop_of |> fromTerm thy |> convertIntrRule state vars 542 fun tac (upd,dup,rot) i = 543 rmtac ctxt upd (if dup then Classical.dup_intr rl else rl) i THEN 544 rot_subgoals_tac (rot, #2 trl) i 545 in (trl, tac) end; 546 547 548val dummyVar = Term.Var (("etc",0), dummyT); 549 550(*Convert from prototerms to ordinary terms with dummy types 551 Ignore abstractions; identify all Vars; STOP at given depth*) 552fun toTerm 0 _ = dummyVar 553 | toTerm d (Const(a,_)) = Term.Const (a,dummyT) (*no need to convert typargs*) 554 | toTerm d (Skolem(a,_)) = Term.Const (a,dummyT) 555 | toTerm d (Free a) = Term.Free (a,dummyT) 556 | toTerm d (Bound i) = Term.Bound i 557 | toTerm d (Var _) = dummyVar 558 | toTerm d (Abs(a,_)) = dummyVar 559 | toTerm d (f $ u) = Term.$ (toTerm d f, toTerm (d-1) u); 560 561(*Too flexible assertions or goals. Motivated by examples such as "(\<And>P. ~P) \<Longrightarrow> 0==1"*) 562fun isVarForm (Var _) = true 563 | isVarForm (Const (c, _) $ Var _) = (c = Data.not_name) 564 | isVarForm _ = false; 565 566fun netMkRules state P vars (nps: Classical.netpair list) = 567 case P of 568 (Const ("*Goal*", _) $ G) => 569 let val pG = mk_Trueprop (toTerm 2 G) 570 val intrs = maps (fn (inet,_) => Net.unify_term inet pG) nps 571 in map (fromIntrRule state vars o #2) (order_list intrs) end 572 | _ => 573 if isVarForm P then [] (*The formula is too flexible, reject*) 574 else 575 let val pP = mk_Trueprop (toTerm 3 P) 576 val elims = maps (fn (_,enet) => Net.unify_term enet pP) nps 577 in map_filter (fromRule state vars o #2) (order_list elims) end; 578 579 580(*Normalize a branch--for tracing*) 581fun norm2 (G,md) = (norm G, md); 582 583fun normLev (Gs,Hs) = (map norm2 Gs, map norm2 Hs); 584 585fun normBr {pairs, lits, vars, lim} = 586 {pairs = map normLev pairs, 587 lits = map norm lits, 588 vars = vars, 589 lim = lim}; 590 591 592val dummyTVar = Term.TVar(("a",0), []); 593val dummyVar2 = Term.Var(("var",0), dummyT); 594 595(*convert blast_tac's type representation to real types for tracing*) 596fun showType (Free a) = Term.TFree (a,[]) 597 | showType (Var _) = dummyTVar 598 | showType t = 599 (case strip_comb t of 600 (Const (a, _), us) => Term.Type(a, map showType us) 601 | _ => dummyT); 602 603(*Display top-level overloading if any*) 604fun topType thy (Const (c, ts)) = SOME (Sign.const_instance thy (c, map showType ts)) 605 | topType thy (Abs(a,t)) = topType thy t 606 | topType thy (f $ u) = (case topType thy f of NONE => topType thy u | some => some) 607 | topType _ _ = NONE; 608 609 610(*Convert from prototerms to ordinary terms with dummy types for tracing*) 611fun showTerm d (Const (a,_)) = Term.Const (a,dummyT) 612 | showTerm d (Skolem(a,_)) = Term.Const (a,dummyT) 613 | showTerm d (Free a) = Term.Free (a,dummyT) 614 | showTerm d (Bound i) = Term.Bound i 615 | showTerm d (Var (Unsynchronized.ref(SOME u))) = showTerm d u 616 | showTerm d (Var (Unsynchronized.ref NONE)) = dummyVar2 617 | showTerm d (Abs(a,t)) = if d=0 then dummyVar 618 else Term.Abs(a, dummyT, showTerm (d-1) t) 619 | showTerm d (f $ u) = if d=0 then dummyVar 620 else Term.$ (showTerm d f, showTerm (d-1) u); 621 622fun string_of ctxt d t = Syntax.string_of_term ctxt (showTerm d t); 623 624(*Convert a Goal to an ordinary Not. Used also in dup_intr, where a goal like 625 Ex(P) is duplicated as the assumption ~Ex(P). *) 626fun negOfGoal (Const ("*Goal*", _) $ G) = negate G 627 | negOfGoal G = G; 628 629fun negOfGoal2 (G,md) = (negOfGoal G, md); 630 631(*Converts all Goals to Nots in the safe parts of a branch. They could 632 have been moved there from the literals list after substitution (equalSubst). 633 There can be at most one--this function could be made more efficient.*) 634fun negOfGoals pairs = map (fn (Gs, unsafe) => (map negOfGoal2 Gs, unsafe)) pairs; 635 636(*Tactic. Convert *Goal* to negated assumption in FIRST position*) 637fun negOfGoal_tac ctxt i = 638 TRACE ctxt Data.ccontr (resolve_tac ctxt [Data.ccontr]) i THEN rotate_tac ~1 i; 639 640fun traceTerm ctxt t = 641 let val thy = Proof_Context.theory_of ctxt 642 val t' = norm (negOfGoal t) 643 val stm = string_of ctxt 8 t' 644 in 645 case topType thy t' of 646 NONE => stm (*no type to attach*) 647 | SOME T => stm ^ " :: " ^ Syntax.string_of_typ ctxt T 648 end; 649 650 651(*Print tracing information at each iteration of prover*) 652fun trace_prover (State {ctxt, fullTrace, ...}) brs = 653 let fun printPairs (((G,_)::_,_)::_) = tracing (traceTerm ctxt G) 654 | printPairs (([],(H,_)::_)::_) = tracing (traceTerm ctxt H ^ " (Unsafe)") 655 | printPairs _ = () 656 fun printBrs (brs0 as {pairs, lits, lim, ...} :: brs) = 657 (fullTrace := brs0 :: !fullTrace; 658 List.app (fn _ => tracing "+") brs; 659 tracing (" [" ^ string_of_int lim ^ "] "); 660 printPairs pairs; 661 tracing "") 662 in if Config.get ctxt trace then printBrs (map normBr brs) else () end; 663 664(*Tracing: variables updated in the last branch operation?*) 665fun traceVars (State {ctxt, ntrail, trail, ...}) ntrl = 666 if Config.get ctxt trace then 667 (case !ntrail-ntrl of 668 0 => () 669 | 1 => tracing " 1 variable UPDATED:" 670 | n => tracing (" " ^ string_of_int n ^ " variables UPDATED:"); 671 (*display the instantiations themselves, though no variable names*) 672 List.app (fn v => tracing (" " ^ string_of ctxt 4 (the (!v)))) 673 (List.take(!trail, !ntrail-ntrl)); 674 tracing "") 675 else (); 676 677(*Tracing: how many new branches are created?*) 678fun traceNew true prems = 679 (case length prems of 680 0 => tracing "branch closed by rule" 681 | 1 => tracing "branch extended (1 new subgoal)" 682 | n => tracing ("branch split: "^ string_of_int n ^ " new subgoals")) 683 | traceNew _ _ = (); 684 685 686 687(*** Code for handling equality: naive substitution, like hyp_subst_tac ***) 688 689(*Replace the ATOMIC term "old" by "new" in t*) 690fun subst_atomic (old,new) t = 691 let fun subst (Var(Unsynchronized.ref(SOME u))) = subst u 692 | subst (Abs(a,body)) = Abs(a, subst body) 693 | subst (f$t) = subst f $ subst t 694 | subst t = if t aconv old then new else t 695 in subst t end; 696 697(*Eta-contract a term from outside: just enough to reduce it to an atom*) 698fun eta_contract_atom (t0 as Abs(a, body)) = 699 (case eta_contract2 body of 700 f $ Bound 0 => if member (op =) (loose_bnos f) 0 then t0 701 else eta_contract_atom (incr_boundvars ~1 f) 702 | _ => t0) 703 | eta_contract_atom t = t 704and eta_contract2 (f$t) = f $ eta_contract_atom t 705 | eta_contract2 t = eta_contract_atom t; 706 707 708(*When can we safely delete the equality? 709 Not if it equates two constants; consider 0=1. 710 Not if it resembles x=t[x], since substitution does not eliminate x. 711 Not if it resembles ?x=0; another goal could instantiate ?x to Suc(i) 712 Prefer to eliminate Bound variables if possible. 713 Result: true = use as is, false = reorient first *) 714 715(*Can t occur in u? For substitution. 716 Does NOT examine the args of Skolem terms: substitution does not affect them. 717 REFLEXIVE because hyp_subst_tac fails on x=x.*) 718fun substOccur t = 719 let (*NO vars are permitted in u except the arguments of t, if it is 720 a Skolem term. This ensures that no equations are deleted that could 721 be instantiated to a cycle. For example, x=?a is rejected because ?a 722 could be instantiated to Suc(x).*) 723 val vars = case t of 724 Skolem(_,vars) => vars 725 | _ => [] 726 fun occEq u = (t aconv u) orelse occ u 727 and occ (Var(Unsynchronized.ref(SOME u))) = occEq u 728 | occ (Var v) = not (mem_var (v, vars)) 729 | occ (Abs(_,u)) = occEq u 730 | occ (f$u) = occEq u orelse occEq f 731 | occ _ = false; 732 in occEq end; 733 734exception DEST_EQ; 735 736(*Take apart an equality. NO constant Trueprop*) 737fun dest_eq (Const (c, _) $ t $ u) = 738 if c = Data.equality_name then (eta_contract_atom t, eta_contract_atom u) 739 else raise DEST_EQ 740 | dest_eq _ = raise DEST_EQ; 741 742(*Reject the equality if u occurs in (or equals!) t*) 743fun check (t,u,v) = if substOccur t u then raise DEST_EQ else v; 744 745(*IF the goal is an equality with a substitutable variable 746 THEN orient that equality ELSE raise exception DEST_EQ*) 747fun orientGoal (t,u) = case (t,u) of 748 (Skolem _, _) => check(t,u,(t,u)) (*eliminates t*) 749 | (_, Skolem _) => check(u,t,(u,t)) (*eliminates u*) 750 | (Free _, _) => check(t,u,(t,u)) (*eliminates t*) 751 | (_, Free _) => check(u,t,(u,t)) (*eliminates u*) 752 | _ => raise DEST_EQ; 753 754(*Substitute through the branch if an equality goal (else raise DEST_EQ). 755 Moves affected literals back into the branch, but it is not clear where 756 they should go: this could make proofs fail.*) 757fun equalSubst ctxt (G, {pairs, lits, vars, lim}) = 758 let val (t,u) = orientGoal(dest_eq G) 759 val subst = subst_atomic (t,u) 760 fun subst2(G,md) = (subst G, md) 761 (*substitute throughout list; extract affected formulae*) 762 fun subForm ((G,md), (changed, pairs)) = 763 let val nG = subst G 764 in if nG aconv G then (changed, (G,md)::pairs) 765 else ((nG,md)::changed, pairs) 766 end 767 (*substitute throughout "stack frame"; extract affected formulae*) 768 fun subFrame ((Gs,Hs), (changed, frames)) = 769 let val (changed', Gs') = List.foldr subForm (changed, []) Gs 770 val (changed'', Hs') = List.foldr subForm (changed', []) Hs 771 in (changed'', (Gs',Hs')::frames) end 772 (*substitute throughout literals; extract affected ones*) 773 fun subLit (lit, (changed, nlits)) = 774 let val nlit = subst lit 775 in if nlit aconv lit then (changed, nlit::nlits) 776 else ((nlit,true)::changed, nlits) 777 end 778 val (changed, lits') = List.foldr subLit ([], []) lits 779 val (changed', pairs') = List.foldr subFrame (changed, []) pairs 780 in if Config.get ctxt trace then tracing ("Substituting " ^ traceTerm ctxt u ^ 781 " for " ^ traceTerm ctxt t ^ " in branch" ) 782 else (); 783 {pairs = (changed',[])::pairs', (*affected formulas, and others*) 784 lits = lits', (*unaffected literals*) 785 vars = vars, 786 lim = lim} 787 end; 788 789 790exception NEWBRANCHES and CLOSEF; 791 792exception PROVE; 793 794(*Trying eq_contr_tac first INCREASES the effort, slowing reconstruction*) 795fun contr_tac ctxt = 796 ematch_tac ctxt [Data.notE] THEN' (eq_assume_tac ORELSE' assume_tac ctxt); 797 798(*Try to unify complementary literals and return the corresponding tactic. *) 799fun tryClose state (G, L) = 800 let 801 val State {ctxt, ...} = state; 802 val eContr_tac = TRACE ctxt Data.notE contr_tac; 803 val eAssume_tac = TRACE ctxt asm_rl (eq_assume_tac ORELSE' assume_tac ctxt); 804 fun close t u tac = if unify state ([], t, u) then SOME tac else NONE; 805 fun arg (_ $ t) = t; 806 in 807 if isGoal G then close (arg G) L eAssume_tac 808 else if isGoal L then close G (arg L) eAssume_tac 809 else if isNot G then close (arg G) L (eContr_tac ctxt) 810 else if isNot L then close G (arg L) (eContr_tac ctxt) 811 else NONE 812 end; 813 814(*Were there Skolem terms in the premise? Must NOT chase Vars*) 815fun hasSkolem (Skolem _) = true 816 | hasSkolem (Abs (_,body)) = hasSkolem body 817 | hasSkolem (f$t) = hasSkolem f orelse hasSkolem t 818 | hasSkolem _ = false; 819 820(*Attach the right "may duplicate" flag to new formulae: if they contain 821 Skolem terms then allow duplication.*) 822fun joinMd md [] = [] 823 | joinMd md (G::Gs) = (G, hasSkolem G orelse md) :: joinMd md Gs; 824 825 826(** Backtracking and Pruning **) 827 828(*clashVar vars (n,trail) determines whether any of the last n elements 829 of "trail" occur in "vars" OR in their instantiations*) 830fun clashVar [] = (fn _ => false) 831 | clashVar vars = 832 let fun clash (0, _) = false 833 | clash (_, []) = false 834 | clash (n, v::vs) = exists (varOccur v) vars orelse clash(n-1,vs) 835 in clash end; 836 837 838(*nbrs = # of branches just prior to closing this one. Delete choice points 839 for goals proved by the latest inference, provided NO variables in the 840 next branch have been updated.*) 841fun prune _ (1, nxtVars, choices) = choices (*DON'T prune at very end: allow 842 backtracking over bad proofs*) 843 | prune (State {ctxt, ntrail, trail, ...}) (nbrs: int, nxtVars, choices) = 844 let fun traceIt last = 845 let val ll = length last 846 and lc = length choices 847 in if Config.get ctxt trace andalso ll<lc then 848 (tracing ("Pruning " ^ string_of_int(lc-ll) ^ " levels"); 849 last) 850 else last 851 end 852 fun pruneAux (last, _, _, []) = last 853 | pruneAux (last, ntrl, trl, (ntrl',nbrs',exn) :: choices) = 854 if nbrs' < nbrs 855 then last (*don't backtrack beyond first solution of goal*) 856 else if nbrs' > nbrs then pruneAux (last, ntrl, trl, choices) 857 else (* nbrs'=nbrs *) 858 if clashVar nxtVars (ntrl-ntrl', trl) then last 859 else (*no clashes: can go back at least this far!*) 860 pruneAux(choices, ntrl', List.drop(trl, ntrl-ntrl'), 861 choices) 862 in traceIt (pruneAux (choices, !ntrail, !trail, choices)) end; 863 864fun nextVars ({pairs, lits, vars, lim} :: _) = map Var vars 865 | nextVars [] = []; 866 867fun backtrack trace (choices as (ntrl, nbrs, exn)::_) = 868 (cond_tracing trace 869 (fn () => "Backtracking; now there are " ^ string_of_int nbrs ^ " branches"); 870 raise exn) 871 | backtrack _ _ = raise PROVE; 872 873(*Add the literal G, handling *Goal* and detecting duplicates.*) 874fun addLit (Const ("*Goal*", _) $ G, lits) = 875 (*New literal is a *Goal*, so change all other Goals to Nots*) 876 let fun bad (Const ("*Goal*", _) $ _) = true 877 | bad (Const (c, _) $ G') = c = Data.not_name andalso G aconv G' 878 | bad _ = false; 879 fun change [] = [] 880 | change (lit :: lits) = 881 (case lit of 882 Const (c, _) $ G' => 883 if c = "*Goal*" orelse c = Data.not_name then 884 if G aconv G' then change lits 885 else negate G' :: change lits 886 else lit :: change lits 887 | _ => lit :: change lits) 888 in 889 Const ("*Goal*", []) $ G :: (if exists bad lits then change lits else lits) 890 end 891 | addLit (G,lits) = ins_term(G, lits) 892 893 894(*For calculating the "penalty" to assess on a branching factor of n 895 log2 seems a little too severe*) 896fun log n = if n<4 then 0 else 1 + log(n div 4); 897 898 899(*match(t,u) says whether the term u might be an instance of the pattern t 900 Used to detect "recursive" rules such as transitivity*) 901fun match (Var _) u = true 902 | match (Const (a,tas)) (Const (b,tbs)) = 903 a = "*Goal*" andalso b = Data.not_name orelse 904 a = Data.not_name andalso b = "*Goal*" orelse 905 a = b andalso matchs tas tbs 906 | match (Free a) (Free b) = (a=b) 907 | match (Bound i) (Bound j) = (i=j) 908 | match (Abs(_,t)) (Abs(_,u)) = match t u 909 | match (f$t) (g$u) = match f g andalso match t u 910 | match t u = false 911and matchs [] [] = true 912 | matchs (t :: ts) (u :: us) = match t u andalso matchs ts us; 913 914 915fun printStats (State {ntried, nclosed, ...}) (b, start, tacs) = 916 if b then 917 tracing (Timing.message (Timing.result start) ^ " for search. Closed: " 918 ^ string_of_int (!nclosed) ^ 919 " tried: " ^ string_of_int (!ntried) ^ 920 " tactics: " ^ string_of_int (length tacs)) 921 else (); 922 923 924(*Tableau prover based on leanTaP. Argument is a list of branches. Each 925 branch contains a list of unexpanded formulae, a list of literals, and a 926 bound on unsafe expansions. 927 "start" is CPU time at start, for printing search time 928*) 929fun prove (state, start, brs, cont) = 930 let val State {ctxt, ntrail, nclosed, ntried, ...} = state; 931 val trace = Config.get ctxt trace; 932 val stats = Config.get ctxt stats; 933 val {safe0_netpair, safep_netpair, unsafe_netpair, ...} = 934 Classical.rep_cs (Classical.claset_of ctxt); 935 val safeList = [safe0_netpair, safep_netpair]; 936 val unsafeList = [unsafe_netpair]; 937 fun prv (tacs, trs, choices, []) = 938 (printStats state (trace orelse stats, start, tacs); 939 cont (tacs, trs, choices)) (*all branches closed!*) 940 | prv (tacs, trs, choices, 941 brs0 as {pairs = ((G,md)::br, unsafe)::pairs, 942 lits, vars, lim} :: brs) = 943 (*apply a safe rule only (possibly allowing instantiation); 944 defer any unsafe formulae*) 945 let exception PRV (*backtrack to precisely this recursion!*) 946 val ntrl = !ntrail 947 val nbrs = length brs0 948 val nxtVars = nextVars brs 949 val G = norm G 950 val rules = netMkRules state G vars safeList 951 (*Make a new branch, decrementing "lim" if instantiations occur*) 952 fun newBr (vars',lim') prems = 953 map (fn prem => 954 if (exists isGoal prem) 955 then {pairs = ((joinMd md prem, []) :: 956 negOfGoals ((br, unsafe)::pairs)), 957 lits = map negOfGoal lits, 958 vars = vars', 959 lim = lim'} 960 else {pairs = ((joinMd md prem, []) :: 961 (br, unsafe) :: pairs), 962 lits = lits, 963 vars = vars', 964 lim = lim'}) 965 prems @ 966 brs 967 (*Seek a matching rule. If unifiable then add new premises 968 to branch.*) 969 fun deeper [] = raise NEWBRANCHES 970 | deeper (((P,prems),tac)::grls) = 971 if unify state (add_term_vars(P,[]), P, G) 972 then (*P comes from the rule; G comes from the branch.*) 973 let val updated = ntrl < !ntrail (*branch updated*) 974 val lim' = if updated 975 then lim - (1+log(length rules)) 976 else lim (*discourage branching updates*) 977 val vars = vars_in_vars vars 978 val vars' = List.foldr add_terms_vars vars prems 979 val choices' = (ntrl, nbrs, PRV) :: choices 980 val tacs' = (tac(updated,false,true)) 981 :: tacs (*no duplication; rotate*) 982 in 983 traceNew trace prems; traceVars state ntrl; 984 (if null prems then (*closed the branch: prune!*) 985 (nclosed := !nclosed + 1; 986 prv(tacs', brs0::trs, 987 prune state (nbrs, nxtVars, choices'), 988 brs)) 989 else (*prems non-null*) 990 if lim'<0 (*faster to kill ALL the alternatives*) 991 then (cond_tracing trace (fn () => "Excessive branching: KILLED"); 992 clearTo state ntrl; raise NEWBRANCHES) 993 else 994 (ntried := !ntried + length prems - 1; 995 prv(tacs', brs0::trs, choices', 996 newBr (vars',lim') prems))) 997 handle PRV => 998 if updated then 999 (*Backtrack at this level. 1000 Reset Vars and try another rule*) 1001 (clearTo state ntrl; deeper grls) 1002 else (*backtrack to previous level*) 1003 backtrack trace choices 1004 end 1005 else deeper grls 1006 (*Try to close branch by unifying with head goal*) 1007 fun closeF [] = raise CLOSEF 1008 | closeF (L::Ls) = 1009 case tryClose state (G,L) of 1010 NONE => closeF Ls 1011 | SOME tac => 1012 let val choices' = 1013 (if trace then (tracing "branch closed"; 1014 traceVars state ntrl) 1015 else (); 1016 prune state (nbrs, nxtVars, 1017 (ntrl, nbrs, PRV) :: choices)) 1018 in nclosed := !nclosed + 1; 1019 prv (tac::tacs, brs0::trs, choices', brs) 1020 handle PRV => 1021 (*reset Vars and try another literal 1022 [this handler is pruned if possible!]*) 1023 (clearTo state ntrl; closeF Ls) 1024 end 1025 (*Try to unify a queued formula (safe or unsafe) with head goal*) 1026 fun closeFl [] = raise CLOSEF 1027 | closeFl ((br, unsafe)::pairs) = 1028 closeF (map fst br) 1029 handle CLOSEF => closeF (map fst unsafe) 1030 handle CLOSEF => closeFl pairs 1031 in 1032 trace_prover state brs0; 1033 if lim<0 then (cond_tracing trace (fn () => "Limit reached."); backtrack trace choices) 1034 else 1035 prv (Data.hyp_subst_tac ctxt trace :: tacs, 1036 brs0::trs, choices, 1037 equalSubst ctxt 1038 (G, {pairs = (br,unsafe)::pairs, 1039 lits = lits, vars = vars, lim = lim}) 1040 :: brs) 1041 handle DEST_EQ => closeF lits 1042 handle CLOSEF => closeFl ((br,unsafe)::pairs) 1043 handle CLOSEF => deeper rules 1044 handle NEWBRANCHES => 1045 (case netMkRules state G vars unsafeList of 1046 [] => (*there are no plausible unsafe rules*) 1047 (cond_tracing trace (fn () => "moving formula to literals"); 1048 prv (tacs, brs0::trs, choices, 1049 {pairs = (br,unsafe)::pairs, 1050 lits = addLit(G,lits), 1051 vars = vars, 1052 lim = lim} :: brs)) 1053 | _ => (*G admits some unsafe rules: try later*) 1054 (cond_tracing trace (fn () => "moving formula to unsafe list"); 1055 prv (if isGoal G then negOfGoal_tac ctxt :: tacs 1056 else tacs, 1057 brs0::trs, 1058 choices, 1059 {pairs = (br, unsafe@[(negOfGoal G, md)])::pairs, 1060 lits = lits, 1061 vars = vars, 1062 lim = lim} :: brs))) 1063 end 1064 | prv (tacs, trs, choices, 1065 {pairs = ([],unsafe)::(Gs,unsafe')::pairs, lits, vars, lim} :: brs) = 1066 (*no more "safe" formulae: transfer unsafe down a level*) 1067 prv (tacs, trs, choices, 1068 {pairs = (Gs,unsafe@unsafe')::pairs, 1069 lits = lits, 1070 vars = vars, 1071 lim = lim} :: brs) 1072 | prv (tacs, trs, choices, 1073 brs0 as {pairs = [([], (H,md)::Hs)], 1074 lits, vars, lim} :: brs) = 1075 (*no safe steps possible at any level: apply a unsafe rule*) 1076 let exception PRV (*backtrack to precisely this recursion!*) 1077 val H = norm H 1078 val ntrl = !ntrail 1079 val rules = netMkRules state H vars unsafeList 1080 (*new premises of unsafe rules may NOT be duplicated*) 1081 fun newPrem (vars,P,dup,lim') prem = 1082 let val Gs' = map (fn Q => (Q,false)) prem 1083 and Hs' = if dup then Hs @ [(negOfGoal H, md)] else Hs 1084 and lits' = if (exists isGoal prem) 1085 then map negOfGoal lits 1086 else lits 1087 in {pairs = if exists (match P) prem then [(Gs',Hs')] 1088 (*Recursive in this premise. Don't make new 1089 "stack frame". New unsafe premises will end up 1090 at the BACK of the queue, preventing 1091 exclusion of others*) 1092 else [(Gs',[]), ([],Hs')], 1093 lits = lits', 1094 vars = vars, 1095 lim = lim'} 1096 end 1097 fun newBr x prems = map (newPrem x) prems @ brs 1098 (*Seek a matching rule. If unifiable then add new premises 1099 to branch.*) 1100 fun deeper [] = raise NEWBRANCHES 1101 | deeper (((P,prems),tac)::grls) = 1102 if unify state (add_term_vars(P,[]), P, H) 1103 then 1104 let val updated = ntrl < !ntrail (*branch updated*) 1105 val vars = vars_in_vars vars 1106 val vars' = List.foldr add_terms_vars vars prems 1107 (*duplicate H if md permits*) 1108 val dup = md (*earlier had "andalso vars' <> vars": 1109 duplicate only if the subgoal has new vars*) 1110 (*any instances of P in the subgoals? 1111 NB: boolean "recur" affects tracing only!*) 1112 and recur = exists (exists (match P)) prems 1113 val lim' = (*Decrement "lim" extra if updates occur*) 1114 if updated then lim - (1+log(length rules)) 1115 else lim-1 1116 (*It is tempting to leave "lim" UNCHANGED if 1117 both dup and recur are false. Proofs are 1118 found at shallower depths, but looping 1119 occurs too often...*) 1120 val mayUndo = 1121 (*Allowing backtracking from a rule application 1122 if other matching rules exist, if the rule 1123 updated variables, or if the rule did not 1124 introduce new variables. This latter condition 1125 means it is not a standard "gamma-rule" but 1126 some other form of unsafe rule. Aim is to 1127 emulate Fast_tac, which allows all unsafe steps 1128 to be undone.*) 1129 not(null grls) (*other rules to try?*) 1130 orelse updated 1131 orelse vars=vars' (*no new Vars?*) 1132 val tac' = tac(updated, dup, true) 1133 (*if recur then perhaps shouldn't call rotate_tac: new 1134 formulae should be last, but that's WRONG if the new 1135 formulae are Goals, since they remain in the first 1136 position*) 1137 1138 in 1139 if lim'<0 andalso not (null prems) 1140 then (*it's faster to kill ALL the alternatives*) 1141 (cond_tracing trace (fn () => "Excessive branching: KILLED"); 1142 clearTo state ntrl; raise NEWBRANCHES) 1143 else 1144 traceNew trace prems; 1145 cond_tracing (trace andalso dup) (fn () => " (duplicating)"); 1146 cond_tracing (trace andalso recur) (fn () => " (recursive)"); 1147 traceVars state ntrl; 1148 if null prems then nclosed := !nclosed + 1 1149 else ntried := !ntried + length prems - 1; 1150 prv(tac' :: tacs, 1151 brs0::trs, 1152 (ntrl, length brs0, PRV) :: choices, 1153 newBr (vars', P, dup, lim') prems) 1154 handle PRV => 1155 if mayUndo 1156 then (*reset Vars and try another rule*) 1157 (clearTo state ntrl; deeper grls) 1158 else (*backtrack to previous level*) 1159 backtrack trace choices 1160 end 1161 else deeper grls 1162 in 1163 trace_prover state brs0; 1164 if lim<1 then (cond_tracing trace (fn () => "Limit reached."); backtrack trace choices) 1165 else deeper rules 1166 handle NEWBRANCHES => 1167 (*cannot close branch: move H to literals*) 1168 prv (tacs, brs0::trs, choices, 1169 {pairs = [([], Hs)], 1170 lits = H::lits, 1171 vars = vars, 1172 lim = lim} :: brs) 1173 end 1174 | prv (tacs, trs, choices, _ :: brs) = backtrack trace choices 1175 in prv ([], [], [(!ntrail, length brs, PROVE)], brs) end; 1176 1177 1178(*Construct an initial branch.*) 1179fun initBranch (ts,lim) = 1180 {pairs = [(map (fn t => (t,true)) ts, [])], 1181 lits = [], 1182 vars = add_terms_vars (ts,[]), 1183 lim = lim}; 1184 1185 1186(*** Conversion & Skolemization of the Isabelle proof state ***) 1187 1188(*Make a list of all the parameters in a subgoal, even if nested*) 1189local open Term 1190in 1191fun discard_foralls (Const(\<^const_name>\<open>Pure.all\<close>,_)$Abs(a,T,t)) = discard_foralls t 1192 | discard_foralls t = t; 1193end; 1194 1195(*List of variables not appearing as arguments to the given parameter*) 1196fun getVars [] i = [] 1197 | getVars ((_,(v,is))::alist) (i: int) = 1198 if member (op =) is i then getVars alist i 1199 else v :: getVars alist i; 1200 1201exception TRANS of string; 1202 1203(*Translation of a subgoal: Skolemize all parameters*) 1204fun fromSubgoal (state as State {ctxt, ...}) t = 1205 let val thy = Proof_Context.theory_of ctxt 1206 val alistVar = Unsynchronized.ref [] 1207 and alistTVar = Unsynchronized.ref [] 1208 fun hdvar ((ix,(v,is))::_) = v 1209 fun from lev t = 1210 let val (ht,ts) = Term.strip_comb t 1211 fun apply u = list_comb (u, map (from lev) ts) 1212 fun bounds [] = [] 1213 | bounds (Term.Bound i::ts) = 1214 if i<lev then raise TRANS 1215 "Function unknown's argument not a parameter" 1216 else i-lev :: bounds ts 1217 | bounds ts = raise TRANS 1218 "Function unknown's argument not a bound variable" 1219 in 1220 case ht of 1221 Term.Const aT => apply (fromConst thy alistTVar aT) 1222 | Term.Free (a,_) => apply (Free a) 1223 | Term.Bound i => apply (Bound i) 1224 | Term.Var (ix,_) => 1225 (case (AList.lookup (op =) (!alistVar) ix) of 1226 NONE => (alistVar := (ix, (Unsynchronized.ref NONE, bounds ts)) 1227 :: !alistVar; 1228 Var (hdvar(!alistVar))) 1229 | SOME(v,is) => if is=bounds ts then Var v 1230 else raise TRANS 1231 ("Discrepancy among occurrences of " 1232 ^ Term.string_of_vname ix)) 1233 | Term.Abs (a,_,body) => 1234 if null ts then Abs(a, from (lev+1) body) 1235 else raise TRANS "argument not in normal form" 1236 end 1237 1238 val npars = length (Logic.strip_params t) 1239 1240 (*Skolemize a subgoal from a proof state*) 1241 fun skoSubgoal i t = 1242 if i<npars then 1243 skoSubgoal (i+1) 1244 (subst_bound (Skolem (gensym state "T", getVars (!alistVar) i), t)) 1245 else t 1246 1247 in skoSubgoal 0 (from 0 (discard_foralls t)) end; 1248 1249 1250(*Tableau engine and proof reconstruction operating on subgoal 1. 1251 "start" is CPU time at start, for printing SEARCH time (also prints reconstruction time) 1252 "lim" is depth limit.*) 1253fun raw_blast start ctxt lim st = 1254 let val state = initialize ctxt 1255 val trace = Config.get ctxt trace; 1256 val stats = Config.get ctxt stats; 1257 val skoprem = fromSubgoal state (#1 (Logic.dest_implies (Thm.prop_of st))) 1258 val hyps = strip_imp_prems skoprem 1259 and concl = strip_imp_concl skoprem 1260 fun cont (tacs,_,choices) = 1261 let val start = Timing.start () 1262 in 1263 case Seq.pull(EVERY' (rev tacs) 1 st) of 1264 NONE => (cond_tracing trace (fn () => "PROOF FAILED for depth " ^ string_of_int lim); 1265 backtrack trace choices) 1266 | cell => (cond_tracing (trace orelse stats) 1267 (fn () => Timing.message (Timing.result start) ^ " for reconstruction"); 1268 Seq.make(fn()=> cell)) 1269 end handle TERM _ => 1270 (cond_tracing trace (fn () => "PROOF RAISED EXN TERM for depth " ^ string_of_int lim); 1271 backtrack trace choices) 1272 in 1273 prove (state, start, [initBranch (mkGoal concl :: hyps, lim)], cont) 1274 end 1275 handle PROVE => Seq.empty 1276 | TRANS s => (cond_tracing (Config.get ctxt trace) (fn () => "Blast: " ^ s); Seq.empty); 1277 1278fun depth_tac ctxt lim i st = 1279 SELECT_GOAL 1280 (Object_Logic.atomize_prems_tac ctxt 1 THEN 1281 raw_blast (Timing.start ()) ctxt lim) i st; 1282 1283fun blast_tac ctxt i st = 1284 let 1285 val start = Timing.start (); 1286 val lim = Config.get ctxt depth_limit; 1287 in 1288 SELECT_GOAL 1289 (Object_Logic.atomize_prems_tac ctxt 1 THEN 1290 DEEPEN (1, lim) (fn m => fn _ => raw_blast start ctxt m) 0 1) i st 1291 end; 1292 1293 1294 1295(*** For debugging: these apply the prover to a subgoal and return 1296 the resulting tactics, trace, etc. ***) 1297 1298(*Read a string to make an initial, singleton branch*) 1299fun readGoal ctxt s = 1300 Syntax.read_prop ctxt s |> fromTerm (Proof_Context.theory_of ctxt) |> rand |> mkGoal; 1301 1302fun tryIt ctxt lim s = 1303 let 1304 val state as State {fullTrace, ...} = initialize ctxt; 1305 val res = timeap prove (state, Timing.start (), [initBranch ([readGoal ctxt s], lim)], I); 1306 in {fullTrace = !fullTrace, result = res} end; 1307 1308 1309 1310(** method setup **) 1311 1312val _ = 1313 Theory.setup 1314 (Method.setup \<^binding>\<open>blast\<close> 1315 (Scan.lift (Scan.option Parse.nat) --| Method.sections Classical.cla_modifiers >> 1316 (fn NONE => SIMPLE_METHOD' o blast_tac 1317 | SOME lim => (fn ctxt => SIMPLE_METHOD' (depth_tac ctxt lim)))) 1318 "classical tableau prover"); 1319 1320end; 1321