src/HOL/Tools/res_atp.ML
author paulson
Wed, 02 Jan 2008 12:22:38 +0100
changeset 25761 466e714de2fc
parent 25492 4cc7976948ac
child 26278 f0c6839df608
permissions -rw-r--r--
testing for empty sort

(*  Author: Jia Meng, Cambridge University Computer Laboratory, NICTA
    ID: $Id$
    Copyright 2004 University of Cambridge

ATPs with TPTP format input.
*)

signature RES_ATP =
sig
  val prover: ResReconstruct.atp ref
  val destdir: string ref
  val helper_path: string -> string -> string
  val problem_name: string ref
  val time_limit: int ref
  val set_prover: string -> unit

  datatype mode = Auto | Fol | Hol
  val linkup_logic_mode : mode ref
  val write_subgoal_file: bool -> mode -> Proof.context -> thm list -> thm list -> int -> string
  val cond_rm_tmp: string -> unit
  val include_all: bool ref
  val run_relevance_filter: bool ref
  val run_blacklist_filter: bool ref
  val theory_const : bool ref
  val pass_mark    : real ref
  val convergence  : real ref
  val max_new      : int ref
  val max_sledgehammers : int ref
  val follow_defs  : bool ref
  val add_all : unit -> unit
  val add_claset : unit -> unit
  val add_simpset : unit -> unit
  val add_clasimp : unit -> unit
  val add_atpset : unit -> unit
  val rm_all : unit -> unit
  val rm_claset : unit -> unit
  val rm_simpset : unit -> unit
  val rm_atpset : unit -> unit
  val rm_clasimp : unit -> unit
  val tvar_classes_of_terms : term list -> string list
  val tfree_classes_of_terms : term list -> string list
  val type_consts_of_terms : theory -> term list -> string list
end;

structure ResAtp: RES_ATP =
struct

structure Recon = ResReconstruct;

fun timestamp s = Output.debug (fn () => ("At " ^ Time.toString (Time.now()) ^ ": " ^ s));

(********************************************************************)
(* some settings for both background automatic ATP calling procedure*)
(* and also explicit ATP invocation methods                         *)
(********************************************************************)

(*** background linkup ***)
val run_blacklist_filter = ref true;
val time_limit = ref 60;
val prover = ref Recon.E;
val max_sledgehammers = ref 1;


(*** relevance filter parameters ***)
val run_relevance_filter = ref true;
val theory_const = ref true;
val pass_mark = ref 0.5;
val convergence = ref 3.2;    (*Higher numbers allow longer inference chains*)
val max_new = ref 60;         (*Limits how many clauses can be picked up per stage*)
val follow_defs = ref false;  (*Follow definitions. Makes problems bigger.*)

fun set_prover atp =
  case String.map Char.toLower atp of
      "e" =>
          (max_new := 100;
           theory_const := false;
           prover := Recon.E)
    | "spass" =>
          (max_new := 40;
           theory_const := true;
           prover := Recon.SPASS)
    | "vampire" =>
          (max_new := 60;
           theory_const := false;
           prover := Recon.Vampire)
    | _ => error ("No such prover: " ^ atp);

val _ = set_prover "E"; (* use E as the default prover *)

val destdir = ref "";   (*Empty means write files to /tmp*)
val problem_name = ref "prob";

(*Return the path to a "helper" like SPASS or tptp2X, first checking that
  it exists.  FIXME: modify to use Path primitives and move to some central place.*)
fun helper_path evar base =
  case getenv evar of
      "" => error  ("Isabelle environment variable " ^ evar ^ " not defined")
    | home =>
        let val path = home ^ "/" ^ base
        in  if File.exists (File.explode_platform_path path) then path
            else error ("Could not find the file " ^ path)
        end;

fun probfile_nosuffix _ =
  if !destdir = "" then File.platform_path (File.tmp_path (Path.basic (!problem_name)))
  else if File.exists (File.explode_platform_path (!destdir))
  then !destdir ^ "/" ^ !problem_name
  else error ("No such directory: " ^ !destdir);

fun prob_pathname n = probfile_nosuffix n ^ "_" ^ Int.toString n;

fun atp_input_file () =
    let val file = !problem_name
    in
        if !destdir = "" then File.platform_path (File.tmp_path (Path.basic file))
        else if File.exists (File.explode_platform_path (!destdir))
        then !destdir ^ "/" ^ file
        else error ("No such directory: " ^ !destdir)
    end;

val include_all = ref true;
val include_simpset = ref false;
val include_claset = ref false;
val include_atpset = ref true;

(*Tests show that follow_defs gives VERY poor results with "include_all"*)
fun add_all() = (include_all:=true; follow_defs := false);
fun rm_all() = include_all:=false;

fun add_simpset() = include_simpset:=true;
fun rm_simpset() = include_simpset:=false;

fun add_claset() = include_claset:=true;
fun rm_claset() = include_claset:=false;

fun add_clasimp() = (include_simpset:=true;include_claset:=true);
fun rm_clasimp() = (include_simpset:=false;include_claset:=false);

fun add_atpset() = include_atpset:=true;
fun rm_atpset() = include_atpset:=false;

fun strip_Trueprop (Const("Trueprop",_) $ t) = t
  | strip_Trueprop t = t;


(***************************************************************)
(* Relevance Filtering                                         *)
(***************************************************************)

(*A surprising number of theorems contain only a few significant constants.
  These include all induction rules, and other general theorems. Filtering
  theorems in clause form reveals these complexities in the form of Skolem 
  functions. If we were instead to filter theorems in their natural form,
  some other method of measuring theorem complexity would become necessary.*)

fun log_weight2 (x:real) = 1.0 + 2.0/Math.ln (x+1.0);

(*The default seems best in practice. A constant function of one ignores
  the constant frequencies.*)
val weight_fn = ref log_weight2;


(*Including equality in this list might be expected to stop rules like subset_antisym from
  being chosen, but for some reason filtering works better with them listed. The
  logical signs All, Ex, &, and --> are omitted because any remaining occurrrences
  must be within comprehensions.*)
val standard_consts = ["Trueprop","==>","all","==","op |","Not","op ="];


(*** constants with types ***)

(*An abstraction of Isabelle types*)
datatype const_typ =  CTVar | CType of string * const_typ list

(*Is the second type an instance of the first one?*)
fun match_type (CType(con1,args1)) (CType(con2,args2)) = 
      con1=con2 andalso match_types args1 args2
  | match_type CTVar _ = true
  | match_type _ CTVar = false
and match_types [] [] = true
  | match_types (a1::as1) (a2::as2) = match_type a1 a2 andalso match_types as1 as2;

(*Is there a unifiable constant?*)
fun uni_mem gctab (c,c_typ) =
  case Symtab.lookup gctab c of
      NONE => false
    | SOME ctyps_list => exists (match_types c_typ) ctyps_list;
  
(*Maps a "real" type to a const_typ*)
fun const_typ_of (Type (c,typs)) = CType (c, map const_typ_of typs) 
  | const_typ_of (TFree _) = CTVar
  | const_typ_of (TVar _) = CTVar

(*Pairs a constant with the list of its type instantiations (using const_typ)*)
fun const_with_typ thy (c,typ) = 
    let val tvars = Sign.const_typargs thy (c,typ)
    in (c, map const_typ_of tvars) end
    handle TYPE _ => (c,[]);   (*Variable (locale constant): monomorphic*)   

(*Add a const/type pair to the table, but a [] entry means a standard connective,
  which we ignore.*)
fun add_const_typ_table ((c,ctyps), tab) =
  Symtab.map_default (c, [ctyps]) (fn [] => [] | ctyps_list => insert (op =) ctyps ctyps_list) 
    tab;

(*Free variables are included, as well as constants, to handle locales*)
fun add_term_consts_typs_rm thy (Const(c, typ), tab) =
      add_const_typ_table (const_with_typ thy (c,typ), tab) 
  | add_term_consts_typs_rm thy (Free(c, typ), tab) =
      add_const_typ_table (const_with_typ thy (c,typ), tab) 
  | add_term_consts_typs_rm thy (t $ u, tab) =
      add_term_consts_typs_rm thy (t, add_term_consts_typs_rm thy (u, tab))
  | add_term_consts_typs_rm thy (Abs(_,_,t), tab) = add_term_consts_typs_rm thy (t, tab)
  | add_term_consts_typs_rm thy (_, tab) = tab;

(*The empty list here indicates that the constant is being ignored*)
fun add_standard_const (s,tab) = Symtab.update (s,[]) tab;

val null_const_tab : const_typ list list Symtab.table = 
    foldl add_standard_const Symtab.empty standard_consts;

fun get_goal_consts_typs thy = foldl (add_term_consts_typs_rm thy) null_const_tab;

(*Inserts a dummy "constant" referring to the theory name, so that relevance
  takes the given theory into account.*)
fun const_prop_of th =
 if !theory_const then
  let val name = Context.theory_name (theory_of_thm th)
      val t = Const (name ^ ". 1", HOLogic.boolT)
  in  t $ prop_of th  end
 else prop_of th;

(**** Constant / Type Frequencies ****)

(*A two-dimensional symbol table counts frequencies of constants. It's keyed first by
  constant name and second by its list of type instantiations. For the latter, we need
  a linear ordering on type const_typ list.*)
  
local

fun cons_nr CTVar = 0
  | cons_nr (CType _) = 1;

in

fun const_typ_ord TU =
  case TU of
    (CType (a, Ts), CType (b, Us)) =>
      (case fast_string_ord(a,b) of EQUAL => dict_ord const_typ_ord (Ts,Us) | ord => ord)
  | (T, U) => int_ord (cons_nr T, cons_nr U);

end;

structure CTtab = TableFun(type key = const_typ list val ord = dict_ord const_typ_ord);

fun count_axiom_consts thy ((thm,_), tab) = 
  let fun count_const (a, T, tab) =
	let val (c, cts) = const_with_typ thy (a,T)
	in  (*Two-dimensional table update. Constant maps to types maps to count.*)
	    Symtab.map_default (c, CTtab.empty) 
	                       (CTtab.map_default (cts,0) (fn n => n+1)) tab
	end
      fun count_term_consts (Const(a,T), tab) = count_const(a,T,tab)
	| count_term_consts (Free(a,T), tab) = count_const(a,T,tab)
	| count_term_consts (t $ u, tab) =
	    count_term_consts (t, count_term_consts (u, tab))
	| count_term_consts (Abs(_,_,t), tab) = count_term_consts (t, tab)
	| count_term_consts (_, tab) = tab
  in  count_term_consts (const_prop_of thm, tab)  end;


(**** Actual Filtering Code ****)

(*The frequency of a constant is the sum of those of all instances of its type.*)
fun const_frequency ctab (c, cts) =
  let val pairs = CTtab.dest (the (Symtab.lookup ctab c))
      fun add ((cts',m), n) = if match_types cts cts' then m+n else n
  in  List.foldl add 0 pairs  end;

(*Add in a constant's weight, as determined by its frequency.*)
fun add_ct_weight ctab ((c,T), w) =
  w + !weight_fn (real (const_frequency ctab (c,T)));

(*Relevant constants are weighted according to frequency, 
  but irrelevant constants are simply counted. Otherwise, Skolem functions,
  which are rare, would harm a clause's chances of being picked.*)
fun clause_weight ctab gctyps consts_typs =
    let val rel = filter (uni_mem gctyps) consts_typs
        val rel_weight = List.foldl (add_ct_weight ctab) 0.0 rel
    in
	rel_weight / (rel_weight + real (length consts_typs - length rel))
    end;
    
(*Multiplies out to a list of pairs: 'a * 'b list -> ('a * 'b) list -> ('a * 'b) list*)
fun add_expand_pairs (x,ys) xys = foldl (fn (y,acc) => (x,y)::acc) xys ys;

fun consts_typs_of_term thy t = 
  let val tab = add_term_consts_typs_rm thy (t, null_const_tab)
  in  Symtab.fold add_expand_pairs tab []  end;

fun pair_consts_typs_axiom thy (thm,name) =
    ((thm,name), (consts_typs_of_term thy (const_prop_of thm)));

exception ConstFree;
fun dest_ConstFree (Const aT) = aT
  | dest_ConstFree (Free aT) = aT
  | dest_ConstFree _ = raise ConstFree;

(*Look for definitions of the form f ?x1 ... ?xn = t, but not reversed.*)
fun defines thy (thm,(name,n)) gctypes =
    let val tm = prop_of thm
	fun defs lhs rhs =
            let val (rator,args) = strip_comb lhs
		val ct = const_with_typ thy (dest_ConstFree rator)
            in  forall is_Var args andalso uni_mem gctypes ct andalso
                Term.add_vars rhs [] subset Term.add_vars lhs []
            end
	    handle ConstFree => false
    in    
	case tm of Const ("Trueprop",_) $ (Const("op =",_) $ lhs $ rhs) => 
		   defs lhs rhs andalso
		   (Output.debug (fn () => "Definition found: " ^ name ^ "_" ^ Int.toString n); true)
		 | _ => false
    end;

type annotd_cls = (thm * (string * int)) * ((string * const_typ list) list);
       
(*For a reverse sort, putting the largest values first.*)
fun compare_pairs ((_,w1),(_,w2)) = Real.compare (w2,w1);

(*Limit the number of new clauses, to prevent runaway acceptance.*)
fun take_best (newpairs : (annotd_cls*real) list) =
  let val nnew = length newpairs
  in
    if nnew <= !max_new then (map #1 newpairs, [])
    else 
      let val cls = sort compare_pairs newpairs
          val accepted = List.take (cls, !max_new)
      in
        Output.debug (fn () => ("Number of candidates, " ^ Int.toString nnew ^ 
		       ", exceeds the limit of " ^ Int.toString (!max_new)));
        Output.debug (fn () => ("Effective pass mark: " ^ Real.toString (#2 (List.last accepted))));
        Output.debug (fn () => "Actually passed: " ^
          space_implode ", " (map (fn (((_,(name,_)),_),_) => name) accepted));

	(map #1 accepted, map #1 (List.drop (cls, !max_new)))
      end
  end;

fun relevant_clauses thy ctab p rel_consts =
  let fun relevant ([],_) [] = [] : (thm * (string * int)) list  (*Nothing added this iteration*)
	| relevant (newpairs,rejects) [] =
	    let val (newrels,more_rejects) = take_best newpairs
		val new_consts = List.concat (map #2 newrels)
		val rel_consts' = foldl add_const_typ_table rel_consts new_consts
		val newp = p + (1.0-p) / !convergence
	    in
              Output.debug (fn () => ("relevant this iteration: " ^ Int.toString (length newrels)));
	       (map #1 newrels) @ 
	       (relevant_clauses thy ctab newp rel_consts' (more_rejects@rejects))
	    end
	| relevant (newrels,rejects) ((ax as (clsthm as (_,(name,n)),consts_typs)) :: axs) =
	    let val weight = clause_weight ctab rel_consts consts_typs
	    in
	      if p <= weight orelse (!follow_defs andalso defines thy clsthm rel_consts)
	      then (Output.debug (fn () => (name ^ " clause " ^ Int.toString n ^ 
	                                    " passes: " ^ Real.toString weight));
	            relevant ((ax,weight)::newrels, rejects) axs)
	      else relevant (newrels, ax::rejects) axs
	    end
    in  Output.debug (fn () => ("relevant_clauses, current pass mark = " ^ Real.toString p));
        relevant ([],[]) 
    end;
	
fun relevance_filter thy axioms goals = 
 if !run_relevance_filter andalso !pass_mark >= 0.1
 then
  let val _ = Output.debug (fn () => "Start of relevance filtering");
      val const_tab = List.foldl (count_axiom_consts thy) Symtab.empty axioms
      val goal_const_tab = get_goal_consts_typs thy goals
      val _ = Output.debug (fn () => ("Initial constants: " ^
                                 space_implode ", " (Symtab.keys goal_const_tab)));
      val rels = relevant_clauses thy const_tab (!pass_mark) 
                   goal_const_tab  (map (pair_consts_typs_axiom thy) axioms)
  in
      Output.debug (fn () => ("Total relevant: " ^ Int.toString (length rels)));
      rels
  end
 else axioms;

(***************************************************************)
(* Retrieving and filtering lemmas                             *)
(***************************************************************)

(*** white list and black list of lemmas ***)

(*The rule subsetI is frequently omitted by the relevance filter. This could be theory data
  or identified with ATPset (which however is too big currently)*)
val whitelist = [subsetI];

(*** retrieve lemmas from clasimpset and atpset, may filter them ***)

(*Hashing to detect duplicate and variant clauses, e.g. from the [iff] attribute*)

fun setinsert (x,s) = Symtab.update (x,()) s;

(*Reject theorems with names like "List.filter.filter_list_def" or
  "Accessible_Part.acc.defs", as these are definitions arising from packages.*)
fun is_package_def a =
  let val names = NameSpace.explode a
  in
     length names > 2 andalso
     not (hd names = "local") andalso
     String.isSuffix "_def" a  orelse  String.isSuffix "_defs" a
  end;

(** a hash function from Term.term to int, and also a hash table **)
val xor_words = List.foldl Word.xorb 0w0;

fun hashw_term ((Const(c,_)), w) = Polyhash.hashw_string (c,w)
  | hashw_term ((Free(a,_)), w) = Polyhash.hashw_string (a,w)
  | hashw_term ((Var(_,_)), w) = w
  | hashw_term ((Bound i), w) = Polyhash.hashw_int(i,w)
  | hashw_term ((Abs(_,_,t)), w) = hashw_term (t, w)
  | hashw_term ((P$Q), w) = hashw_term (Q, (hashw_term (P, w)));

fun hash_literal (Const("Not",_)$P) = Word.notb(hashw_term(P,0w0))
  | hash_literal P = hashw_term(P,0w0);

fun hash_term t = Word.toIntX (xor_words (map hash_literal (HOLogic.disjuncts (strip_Trueprop t))));

fun equal_thm (thm1,thm2) = Term.aconv(prop_of thm1, prop_of thm2);

exception HASH_CLAUSE;

(*Create a hash table for clauses, of the given size*)
fun mk_clause_table n =
      Polyhash.mkTable (hash_term o prop_of, equal_thm)
                       (n, HASH_CLAUSE);

(*Use a hash table to eliminate duplicates from xs. Argument is a list of
  (thm * (string * int)) tuples. The theorems are hashed into the table. *)
fun make_unique xs =
  let val ht = mk_clause_table 7000
  in
      Output.debug (fn () => ("make_unique gets " ^ Int.toString (length xs) ^ " clauses"));
      app (ignore o Polyhash.peekInsert ht) xs;
      Polyhash.listItems ht
  end;

(*Remove existing axiom clauses from the conjecture clauses, as this can dramatically
  boost an ATP's performance (for some reason)*)
fun subtract_cls c_clauses ax_clauses =
  let val ht = mk_clause_table 2200
      fun known x = isSome (Polyhash.peek ht x)
  in
      app (ignore o Polyhash.peekInsert ht) ax_clauses;
      filter (not o known) c_clauses
  end;

fun all_valid_thms ctxt =
  let
    fun blacklisted s = !run_blacklist_filter andalso is_package_def s

    fun extern_valid space (name, ths) =
      let
        val is_valid = ProofContext.valid_thms ctxt;
        val xname = NameSpace.extern space name;
      in
        if blacklisted name then I
        else if is_valid (xname, ths) then cons (xname, filter_out ResAxioms.bad_for_atp ths)
        else if is_valid (name, ths) then cons (name, filter_out ResAxioms.bad_for_atp ths)
        else I
      end;
    val thy = ProofContext.theory_of ctxt;
    val all_thys = thy :: Theory.ancestors_of thy;

    fun dest_valid (space, tab) = Symtab.fold (extern_valid space) tab [];
  in
    maps (dest_valid o PureThy.theorems_of) all_thys @
    fold (extern_valid (#1 (ProofContext.theorems_of ctxt)))
      (FactIndex.find (ProofContext.fact_index_of ctxt) ([], [])) []
  end;

fun multi_name a (th, (n,pairs)) =
  (n+1, (a ^ "(" ^ Int.toString n ^ ")", th) :: pairs)

fun add_single_names ((a, []), pairs) = pairs
  | add_single_names ((a, [th]), pairs) = (a,th)::pairs
  | add_single_names ((a, ths), pairs) = #2 (foldl (multi_name a) (1,pairs) ths);

(*Ignore blacklisted basenames*)
fun add_multi_names ((a, ths), pairs) =
  if (Sign.base_name a) mem_string ResAxioms.multi_base_blacklist  then pairs
  else add_single_names ((a, ths), pairs);

fun is_multi (a, ths) = length ths > 1 orelse String.isSuffix ".axioms" a;

(*The single theorems go BEFORE the multiple ones. Blacklist is applied to all.*)
fun name_thm_pairs ctxt =
  let val (mults,singles) = List.partition is_multi (all_valid_thms ctxt)
      val ht = mk_clause_table 900   (*ht for blacklisted theorems*)
      fun blacklisted x = !run_blacklist_filter andalso isSome (Polyhash.peek ht x)
  in
      app (fn th => ignore (Polyhash.peekInsert ht (th,()))) (ResBlacklist.get ctxt);
      filter (not o blacklisted o #2)
        (foldl add_single_names (foldl add_multi_names [] mults) singles)
  end;

fun check_named ("",th) = (warning ("No name for theorem " ^ string_of_thm th); false)
  | check_named (_,th) = true;

fun display_thm (name,th) = Output.debug (fn () => name ^ ": " ^ string_of_thm th);

(* get lemmas from claset, simpset, atpset and extra supplied rules *)
fun get_clasimp_atp_lemmas ctxt user_thms =
  let val included_thms =
        if !include_all
        then (tap (fn ths => Output.debug
                     (fn () => ("Including all " ^ Int.toString (length ths) ^ " theorems")))
                  (name_thm_pairs ctxt))
        else
        let val claset_thms =
                if !include_claset then ResAxioms.claset_rules_of ctxt
                else []
            val simpset_thms =
                if !include_simpset then ResAxioms.simpset_rules_of ctxt
                else []
            val atpset_thms =
                if !include_atpset then ResAxioms.atpset_rules_of ctxt
                else []
            val _ = (Output.debug (fn () => "ATP theorems: ");  app display_thm atpset_thms)
        in  claset_thms @ simpset_thms @ atpset_thms  end
      val user_rules = filter check_named
                         (map ResAxioms.pairname
                           (if null user_thms then whitelist else user_thms))
  in
      (filter check_named included_thms, user_rules)
  end;

(***************************************************************)
(* Type Classes Present in the Axiom or Conjecture Clauses     *)
(***************************************************************)

fun add_classes (sorts, cset) = foldl setinsert cset (List.concat sorts);

(*Remove this trivial type class*)
fun delete_type cset = Symtab.delete_safe "HOL.type" cset;

fun tvar_classes_of_terms ts =
  let val sorts_list = map (map #2 o term_tvars) ts
  in  Symtab.keys (delete_type (foldl add_classes Symtab.empty sorts_list))  end;

fun tfree_classes_of_terms ts =
  let val sorts_list = map (map #2 o term_tfrees) ts
  in  Symtab.keys (delete_type (foldl add_classes Symtab.empty sorts_list))  end;

(*fold type constructors*)
fun fold_type_consts f (Type (a, Ts)) x = fold (fold_type_consts f) Ts (f (a,x))
  | fold_type_consts f T x = x;

val add_type_consts_in_type = fold_type_consts setinsert;

(*Type constructors used to instantiate overloaded constants are the only ones needed.*)
fun add_type_consts_in_term thy =
  let val const_typargs = Sign.const_typargs thy
      fun add_tcs (Const cT) x = fold add_type_consts_in_type (const_typargs cT) x
        | add_tcs (Abs (_, T, u)) x = add_tcs u x
        | add_tcs (t $ u) x = add_tcs t (add_tcs u x)
        | add_tcs _ x = x
  in  add_tcs  end

fun type_consts_of_terms thy ts =
  Symtab.keys (fold (add_type_consts_in_term thy) ts Symtab.empty);


(***************************************************************)
(* ATP invocation methods setup                                *)
(***************************************************************)

fun cnf_hyps_thms ctxt =
    let val ths = Assumption.prems_of ctxt
    in fold (fold (insert Thm.eq_thm) o ResAxioms.cnf_axiom) ths [] end;

(*Translation mode can be auto-detected, or forced to be first-order or higher-order*)
datatype mode = Auto | Fol | Hol;

val linkup_logic_mode = ref Auto;

(*Ensures that no higher-order theorems "leak out"*)
fun restrict_to_logic thy true cls = filter (Meson.is_fol_term thy o prop_of o fst) cls
  | restrict_to_logic thy false cls = cls;

(**** Predicates to detect unwanted clauses (prolific or likely to cause unsoundness) ****)

(** Too general means, positive equality literal with a variable X as one operand,
  when X does not occur properly in the other operand. This rules out clearly
  inconsistent clauses such as V=a|V=b, though it by no means guarantees soundness. **)

fun occurs ix =
    let fun occ(Var (jx,_)) = (ix=jx)
          | occ(t1$t2)      = occ t1 orelse occ t2
          | occ(Abs(_,_,t)) = occ t
          | occ _           = false
    in occ end;

fun is_recordtype T = not (null (RecordPackage.dest_recTs T));

(*Unwanted equalities include
  (1) those between a variable that does not properly occur in the second operand,
  (2) those between a variable and a record, since these seem to be prolific "cases" thms
*)
fun too_general_eqterms (Var (ix,T), t) = not (occurs ix t) orelse is_recordtype T
  | too_general_eqterms _ = false;

fun too_general_equality (Const ("op =", _) $ x $ y) =
      too_general_eqterms (x,y) orelse too_general_eqterms(y,x)
  | too_general_equality _ = false;

(* tautologous? *)
fun is_taut (Const ("Trueprop", _) $ Const ("True", _)) = true
  | is_taut _ = false;

(*True if the term contains a variable whose (atomic) type is in the given list.*)
fun has_typed_var tycons =
  let fun var_tycon (Var (_, Type(a,_))) = a mem_string tycons
        | var_tycon _ = false
  in  exists var_tycon o term_vars  end;

(*Clauses are forbidden to contain variables of these types. The typical reason is that
  they lead to unsoundness. Note that "unit" satisfies numerous equations like ?X=().
  The resulting clause will have no type constraint, yielding false proofs. Even "bool"
  leads to many unsound proofs, though (obviously) only for higher-order problems.*)
val unwanted_types = ["Product_Type.unit","bool"];

fun unwanted t =
  is_taut t orelse has_typed_var unwanted_types t orelse
  forall too_general_equality (HOLogic.disjuncts (strip_Trueprop t));

(*Clauses containing variables of type "unit" or "bool" are unlikely to be useful and
  likely to lead to unsound proofs.*)
fun remove_unwanted_clauses cls = filter (not o unwanted o prop_of o fst) cls;

(*Called by the oracle-based methods declared in res_atp_methods.ML*)
fun write_subgoal_file dfg mode ctxt conjectures user_thms n =
    let val conj_cls = Meson.make_clauses conjectures
                         |> map ResAxioms.combinators |> Meson.finish_cnf
        val hyp_cls = cnf_hyps_thms ctxt
        val goal_cls = conj_cls@hyp_cls
        val goal_tms = map prop_of goal_cls
        val thy = ProofContext.theory_of ctxt
        val isFO = case mode of
                            Auto => forall (Meson.is_fol_term thy) goal_tms
                          | Fol => true
                          | Hol => false
        val (included_thms,user_rules) = get_clasimp_atp_lemmas ctxt user_thms
        val cla_simp_atp_clauses = included_thms
                                     |> ResAxioms.cnf_rules_pairs |> make_unique
                                     |> restrict_to_logic thy isFO
                                     |> remove_unwanted_clauses
        val user_cls = ResAxioms.cnf_rules_pairs user_rules
        val axclauses = make_unique (user_cls @ relevance_filter thy cla_simp_atp_clauses goal_tms)
        val subs = tfree_classes_of_terms goal_tms
        and axtms = map (prop_of o #1) axclauses
        val supers = tvar_classes_of_terms axtms
        and tycons = type_consts_of_terms thy (goal_tms@axtms)
        (*TFrees in conjecture clauses; TVars in axiom clauses*)
        val (supers',arity_clauses) = ResClause.make_arity_clauses thy tycons supers
        val classrel_clauses = ResClause.make_classrel_clauses thy subs supers'
        val writer = if dfg then ResHolClause.dfg_write_file else ResHolClause.tptp_write_file
        and file = atp_input_file()
        and user_lemmas_names = map #1 user_rules
    in
        writer thy isFO goal_cls file (axclauses,classrel_clauses,arity_clauses) user_lemmas_names;
        Output.debug (fn () => "Writing to " ^ file);
        file
    end;


(**** remove tmp files ****)
fun cond_rm_tmp file =
    if !Output.debugging orelse !destdir <> ""
    then Output.debug (fn () => "ATP input kept...")
    else OS.FileSys.remove file;


(***************************************************************)
(* automatic ATP invocation                                    *)
(***************************************************************)

(* call prover with settings and problem file for the current subgoal *)
fun watcher_call_provers files (childin, childout, pid) =
  let val time = Int.toString (!time_limit)
      fun make_atp_list [] = []
	| make_atp_list (file::files) =
	   (Output.debug (fn () => "problem file in watcher_call_provers is " ^ file);
	    (*options are separated by Watcher.setting_sep, currently #"%"*)
	    case !prover of
		Recon.SPASS =>
		  let val spass = helper_path "SPASS_HOME" "SPASS"
		      val sopts =
       "-Auto%-SOS=1%-PGiven=0%-PProblem=0%-Splits=0%-FullRed=0%-DocProof%-TimeLimit=" ^ time
		  in  ("spass", spass, sopts, file) :: make_atp_list files  end
	      | Recon.Vampire =>
		  let val vampire = helper_path "VAMPIRE_HOME" "vampire"
		      val vopts = "--output_syntax tptp%--mode casc%-t " ^ time
		  in  ("vampire", vampire, vopts, file) :: make_atp_list files  end
	      | Recon.E =>
		  let val eproof = helper_path "E_HOME" "eproof"
		      val eopts = "--tstp-in%--tstp-out%-l5%-xAutoDev%-tAutoDev%--silent%--cpu-limit=" ^ time
		  in  ("E", eproof, eopts, file) :: make_atp_list files  end)
  in
    Watcher.callResProvers(childout, make_atp_list files);
    Output.debug (fn () => "Sent commands to watcher!")
  end

(*For debugging the generated set of theorem names*)
fun trace_vector fname =
  let val path = File.explode_platform_path (fname ^ "_thm_names")
  in  Vector.app (File.append path o (fn s => s ^ "\n"))  end;

(*We write out problem files for each subgoal. Argument probfile generates filenames,
  and allows the suppression of the suffix "_1" in problem-generation mode.
  FIXME: does not cope with &&, and it isn't easy because one could have multiple
  subgoals, each involving &&.*)
fun write_problem_files probfile (ctxt, chain_ths, th) =
  let val goals = Library.take (!max_sledgehammers, Thm.prems_of th)  (*raises no exception*)
      val _ = Output.debug (fn () => "number of subgoals = " ^ Int.toString (length goals))
      val thy = ProofContext.theory_of ctxt
      fun get_neg_subgoals [] _ = []
        | get_neg_subgoals (gl::gls) n = #1 (ResAxioms.neg_conjecture_clauses th n) ::
                                         get_neg_subgoals gls (n+1)
      val goal_cls = get_neg_subgoals goals 1
                     handle THM ("assume: variables", _, _) => 
                       error "Sledgehammer: Goal contains type variables (TVars)"                       
      val isFO = case !linkup_logic_mode of
			  Auto => forall (Meson.is_fol_term thy) (map prop_of (List.concat goal_cls))
			| Fol => true
			| Hol => false
      val (included_thms,white_thms) = get_clasimp_atp_lemmas ctxt chain_ths
      val included_cls = included_thms |> ResAxioms.cnf_rules_pairs |> make_unique
                                       |> restrict_to_logic thy isFO
                                       |> remove_unwanted_clauses
      val _ = Output.debug (fn () => "included clauses = " ^ Int.toString(length included_cls))
      val white_cls = ResAxioms.cnf_rules_pairs white_thms
      (*clauses relevant to goal gl*)
      val axcls_list = map (fn ngcls => white_cls @ relevance_filter thy included_cls (map prop_of ngcls)) goal_cls
      val _ = app (fn axcls => Output.debug (fn () => "filtered clauses = " ^ Int.toString(length axcls)))
                  axcls_list
      val writer = if !prover = Recon.SPASS then ResHolClause.dfg_write_file else ResHolClause.tptp_write_file
      fun write_all [] [] _ = []
        | write_all (ccls::ccls_list) (axcls::axcls_list) k =
            let val fname = probfile k
                val _ = Output.debug (fn () => "About to write file " ^ fname)
                val axcls = make_unique axcls
                val _ = Output.debug (fn () => "Conjecture Clauses (before duplicate removal)")
                val _ = app (fn th => Output.debug (fn _ => string_of_thm th)) ccls
                val ccls = subtract_cls ccls axcls
                val _ = Output.debug (fn () => "Conjecture Clauses (AFTER duplicate removal)")
                val _ = app (fn th => Output.debug (fn _ => string_of_thm th)) ccls
                val ccltms = map prop_of ccls
                and axtms = map (prop_of o #1) axcls
                val subs = tfree_classes_of_terms ccltms
                and supers = tvar_classes_of_terms axtms
                and tycons = type_consts_of_terms thy (ccltms@axtms)
                (*TFrees in conjecture clauses; TVars in axiom clauses*)
                val (supers',arity_clauses) = ResClause.make_arity_clauses thy tycons supers
                val _ = Output.debug (fn () => "arity clauses = " ^ Int.toString (length arity_clauses))
                val classrel_clauses = ResClause.make_classrel_clauses thy subs supers'
                val _ = Output.debug (fn () => "classrel clauses = " ^ Int.toString (length classrel_clauses))
                val clnames = writer thy isFO ccls fname (axcls,classrel_clauses,arity_clauses) []
                val thm_names = Vector.fromList clnames
                val _ = if !Output.debugging then trace_vector fname thm_names else ()
            in  (thm_names,fname) :: write_all ccls_list axcls_list (k+1)  end
      val (thm_names_list, filenames) = ListPair.unzip (write_all goal_cls axcls_list 1)
  in
      (filenames, thm_names_list)
  end;

val last_watcher_pid = ref (NONE : (TextIO.instream * TextIO.outstream *
                                    Posix.Process.pid * string list) option);

fun kill_last_watcher () =
    (case !last_watcher_pid of
         NONE => ()
       | SOME (_, _, pid, files) =>
          (Output.debug (fn () => "Killing old watcher, pid = " ^ string_of_pid pid);
           Watcher.killWatcher pid;
           ignore (map (try cond_rm_tmp) files)))
     handle OS.SysErr _ => Output.debug (fn () => "Attempt to kill watcher failed");

(*writes out the current problems and calls ATPs*)
fun isar_atp (ctxt, chain_ths, th) =
  if Thm.no_prems th then warning "Nothing to prove"
  else
    let
      val _ = kill_last_watcher()
      val (files,thm_names_list) = write_problem_files prob_pathname (ctxt, chain_ths, th)
      val (childin, childout, pid) = Watcher.createWatcher (ctxt, th, thm_names_list)
    in
      last_watcher_pid := SOME (childin, childout, pid, files);
      Output.debug (fn () => "problem files: " ^ space_implode ", " files);
      Output.debug (fn () => "pid: " ^ string_of_pid pid);
      watcher_call_provers files (childin, childout, pid)
    end;

(*For ML scripts, and primarily, for debugging*)
fun callatp () =
  let val th = topthm()
      val ctxt = ProofContext.init (theory_of_thm th)
  in  isar_atp (ctxt, [], th)  end;

val isar_atp_writeonly = PrintMode.setmp []
      (fn (ctxt, chain_ths, th) =>
       if Thm.no_prems th then warning "Nothing to prove"
       else
         let val probfile = if Thm.nprems_of th = 1 then probfile_nosuffix
                            else prob_pathname
         in ignore (write_problem_files probfile (ctxt, chain_ths, th)) end);


(** the Isar toplevel command **)

fun sledgehammer state =
  let
    val (ctxt, (chain_ths, goal)) = Proof.get_goal (Toplevel.proof_of state)
    val chain_ths = map (PureThy.put_name_hint Recon.chained_hint) chain_ths
                    (*Mark the chained theorems to keep them out of metis calls;
                      their original "name hints" may be bad anyway.*)
    val thy = ProofContext.theory_of ctxt
  in
    if exists_type ResAxioms.type_has_empty_sort (prop_of goal)  
    then error "Proof state contains the empty sort" else ();
    Output.debug (fn () => "subgoals in isar_atp:\n" ^
                  Syntax.string_of_term ctxt (Logic.mk_conjunction_list (Thm.prems_of goal)));
    Output.debug (fn () => "current theory: " ^ Context.theory_name thy);
    app (fn th => Output.debug (fn _ => "chained: " ^ string_of_thm th)) chain_ths;
    if !time_limit > 0 then isar_atp (ctxt, chain_ths, goal)
    else (warning ("Writing problem file only: " ^ !problem_name);
          isar_atp_writeonly (ctxt, chain_ths, goal))
  end;

val _ =
  OuterSyntax.command "sledgehammer" "call automatic theorem provers" OuterKeyword.diag
    (Scan.succeed (Toplevel.no_timing o Toplevel.unknown_proof o Toplevel.keep sledgehammer));

end;