(******************************************************************************)
(* FILE          : struct_equal.ml                                            *)
(* DESCRIPTION   : Proof procedure for simplifying an equation between two    *)
(*                 data-structures of the same type.                          *)
(*                                                                            *)
(* READS FILES   : <none>                                                     *)
(* WRITES FILES  : <none>                                                     *)
(*                                                                            *)
(* AUTHOR        : R.J.Boulton & T.F.Melham                                   *)
(* DATE          : 4th June 1992                                              *)
(*                                                                            *)
(* LAST MODIFIED : R.J.Boulton                                                *)
(* DATE          : 14th October 1992                                          *)
(*                                                                            *)
(* LAST MODIFIED : P. Papapanagiotou (University of Edinburgh)                *)
(* DATE          : 2008                                                       *)
(******************************************************************************)

let subst_occs =
  let rec subst_occs slist tm =
    let applic,noway = partition (fun (i,(t,x)) -> aconv tm x) slist in
    let sposs = map (fun (l,z) -> let l1,l2 = partition ((=) 1) l in
                                  (l1,z),(l2,z)) applic in
    let racts,rrest = unzip sposs in
    let acts = filter (fun t -> not (fst t = [])) racts in
    let trest = map (fun (n,t) -> (map (C (-) 1) n,t)) rrest in
    let urest = filter (fun t -> not (fst t = [])) trest in
    let tlist = urest @ noway in
      if acts = [] then
      if is_comb tm then
        let l,r = dest_comb tm in
        let l',s' = subst_occs tlist l in
        let r',s'' = subst_occs s' r in
        mk_comb(l',r'),s''
      else if is_abs tm then
        let bv,bod = dest_abs tm in
        let gv = genvar(type_of bv) in
        let nbod = vsubst[gv,bv] bod in
        let tm',s' = subst_occs tlist nbod in
        alpha bv (mk_abs(gv,tm')),s'
      else
        tm,tlist
    else
      let tm' = (fun (n,(t,x)) -> subst[t,x] tm) (hd acts) in
      tm',tlist in
  fun ilist slist tm -> fst(subst_occs (zip ilist slist) tm);;

let GSUBS substfn ths th =
   let ls = map (lhs o concl) ths
       in let vars = map (genvar o type_of) ls
       in let w = substfn (List.combine ls vars) (concl th)
   in SUBST (List.combine ths vars) w th ;;

let SUBS_OCCS nlths th =
   try (let (nll, ths) = unzip nlths
   in GSUBS (subst_occs nll) ths th
   ) with Failure _ -> failwith "SUBS_OCCS";;

(*----------------------------------------------------------------------------*)
(* VAR_NOT_EQ_STRUCT_OF_VAR_CONV : (thm # thm list # thm list) -> conv        *)
(*                                                                            *)
(* Proof method developed through discussion between                          *)
(* R. Boulton, T. Melham and A. Pitts.                                        *)
(*                                                                            *)
(* This conversion can be used to prove that a variable is not equal to a     *)
(* structure containing that variable as a proper subterm. The structures are *)
(* restricted to applications of constructors from a single recursive type.   *)
(* The leaf nodes must be either variables or 0-ary constructors of the type. *)
(*                                                                            *)
(* The theorems taken as arguments are the induction, distinctness and        *)
(* injectivity theorems for the recursive type, as proved by the functions:   *)
(*                                                                            *)
(*    prove_induction_thm                                                     *)
(*    prove_constructors_distinct                                             *)
(*    prove_constructors_one_one                                              *)
(*                                                                            *)
(* Since the latter two functions may fail, the distinctness and injectivity  *)
(* theorems are passed around as lists of conjuncts, so that a failure        *)
(* results in an empty list.                                                  *)
(*                                                                            *)
(* Examples of input terms:                                                   *)
(*                                                                            *)
(*    ~(l = CONS h l)                                                         *)
(*    ~(CONS h1 (CONS h2 l) = l)                                              *)
(*    ~(n = SUC(SUC(SUC n)))                                                  *)
(*    ~(t = TWO (ONE u) (THREE v (ONE t) (TWO u (ONE t))))                    *)
(*                                                                            *)
(* where the last example is for the type defined by:                         *)
(*                                                                            *)
(*    test = ZERO | ONE test | TWO test test | THREE test test test           *)
(*                                                                            *)
(* The procedure works by first generalising the structure to eliminate any   *)
(* irrelevant substructures. If the variable occurs more than once in the     *)
(* structure the more deeply nested occurrences are replaced by new variables *)
(* because multiple occurrences of the variable prevent the induction from    *)
(* working. The generalised term for the last example is:                     *)
(*                                                                            *)
(*    TWO a (THREE v (ONE t) b)                                               *)
(*                                                                            *)
(* The procedure then forms a conjunction of the inequalities for this term   *)
(* and all of its `rotations':                                                *)
(*                                                                            *)
(*    !t. (!a v b. ~(t = TWO a (THREE v (ONE t) b))) /\                       *)
(*        (!a v b. ~(t = THREE v (ONE (TWO a t)) b)) /\                       *)
(*        (!a v b. ~(t = ONE (TWO a (THREE v t b))))                          *)
(*                                                                            *)
(* This can be proved by a straightforward structural induction. The reason   *)
(* for including the rotations is that the induction hypothesis required for  *)
(* the proof of the original generalised term is the rotation of it.          *)
(*                                                                            *)
(* The procedure could be optimised by detecting duplicated rotations. For    *)
(* example it is not necessary to prove:                                      *)
(*                                                                            *)
(*    !n. ~(n = SUC(SUC(SUC n))) /\                                           *)
(*        ~(n = SUC(SUC(SUC n))) /\                                           *)
(*        ~(n = SUC(SUC(SUC n)))                                              *)
(*                                                                            *)
(* in order to prove "~(n = SUC(SUC(SUC n)))" because the structure is its    *)
(* own rotations. It is sufficient to prove:                                  *)
(*                                                                            *)
(*    !n. ~(n = SUC(SUC(SUC n)))                                              *)
(*                                                                            *)
(* The procedure currently uses backwards proof. It would probably be more    *)
(* efficient to use forwards proof.                                           *)
(*----------------------------------------------------------------------------*)

let VAR_NOT_EQ_STRUCT_OF_VAR_CONV =
try( let number_list l =
    let rec number_list' n l =
       if (l = [])
       then []
       else (hd l,n)::(number_list' (n + 1) (tl l))
    in number_list' 1 l
 in  let name = fst o dest_const
 in  let occurrences constrs v st =
        let rec occurrences' v st path =
           if (not (type_of st = type_of v)) then []
           else if (st = v) then [rev path]
           else if (is_var st) then []
           else let (f,args) =
                  (check ( ((can (C assoc constrs)) o name o fst) )) (strip_comb st)
(* Boulton was using hashI here... but I don't know why *)
                in  flat (map (fun (arg,n) -> occurrences' v arg (n::path))
                                 (number_list args))
        in occurrences' v st []
 in  let min_length l =
        let rec min_length' (x,n) l =
           if (l = [])
           then x
           else if (length (hd l) < n)
                then min_length' (hd l,length (hd l)) (tl l)
                else min_length' (x,n) (tl l)
        in if (l = [])
           then failwith "min_length"
           else min_length' (hd l,length (hd l)) (tl l)
 in  let rec generalise (st,occ) =
        let rec replace_side_structs (n,argn',binding) m args =
           if (args = [])
           then ([],[])
           else let m' = m + 1
                and arg = hd args
                in  let (rest,bind) =
                       replace_side_structs (n,argn',binding) m' (tl args)
                in  if (m' = n) then ((argn'::rest),(binding @ bind))
                    else if (is_var arg) then ((arg::rest),((arg,arg)::bind))
                    else let var = genvar (type_of arg)
                         in  ((var::rest),((var,arg)::bind))
        in if (occ = [])
           then (st,[])
           else let (f,args) = strip_comb st
                and (n::occ') = occ
                in  let (argn',binding) = generalise (el (n-1) args,occ')
                in  let (args',bind) =
                           replace_side_structs (n,argn',binding) 0 args
                in  (list_mk_comb (f,args'),bind)
 in  let rec constr_apps v (st,occ) =
        let rec replace_argn (n,argn') m args =
           if (args = [])
           then []
           else let m' = m + 1
                in  if (m' = n)
                    then argn'::(tl args)
                    else (hd args)::(replace_argn (n,argn') m' (tl args))
        in if (occ = [])
           then []
           else let (f,args) = strip_comb st
                and (n::occ') = occ
                in  let args' = replace_argn (n,v) 0 args
                in  (list_mk_comb (f,args'))::(constr_apps v (el (n-1) args,occ'))
 in  let rotations l =
        let rec rotations' l n =
           if (n < 1)
           then []
           else l::(rotations' ((tl l) @ [hd l]) (n - 1))
        in rotations' l (length l)
 in  let two_constrs = (hash (fst o strip_comb) (fst o strip_comb)) o
                       dest_eq o dest_neg o snd o strip_forall o concl
 in  let flip (x,y) = (y,x)
 in  let DEPTH_SYM = GEN_ALL o NOT_EQ_SYM o SPEC_ALL
 in  let rec arg_types ty =
        try (match (dest_type ty) with
         | ("fun",[argty;rest]) ->  argty::(arg_types rest)
         | _ -> [])
        with Failure _ -> []
 in  let name_and_args = ((hash I) arg_types) o dest_const
 in
 fun (induction,distincts,oneOnes) ->
 let half_distincts = map (fun th -> ((hash name) name) (two_constrs th), th) distincts
 in  let distincts = half_distincts @ (map ((hash flip) DEPTH_SYM) half_distincts)
 in  let ind_goals =
        (conjuncts o fst o dest_imp o snd o dest_forall o concl) induction
 in  let constrs =
        map (name_and_args o fst o strip_comb o rand o snd o strip_forall o
             snd o (splitlist dest_imp) o snd o strip_forall) ind_goals
 in
 fun tm ->
 (let (l,r) = dest_eq (dest_neg tm)
  in  let (flipped,v,st) =
         if (is_var l)
         then if (is_var r) then failwith "" else (false,l,r)
         else if (is_var r)
              then (true,r,l)
              else failwith ""
  in  let occ = min_length (occurrences constrs v st)
  in  let (st',bind) = generalise (st,occ)
  in  let (vars,subterms) = List.split bind
  in  let apps = constr_apps v (st',occ)
  in  let rotats =
         map (end_itlist (fun t1 t2 -> subst [(t2,v)] t1)) (rotations apps)
  in  let uneqs = map (mk_neg o (curry mk_eq v)) rotats
  in  let conj =
         mk_forall (v,list_mk_conj (map (curry list_mk_forall vars) uneqs))
  in  
let th1 =
         prove (conj,INDUCT_TAC_ induction THEN
                     ASM_REWRITE_TAC (oneOnes @ (map snd distincts)))
  in  let th2 = (hd o CONJUNCTS o (SPEC v)) th1
  in  let th3 = SPECL subterms th2
  in  let th4 = if flipped then (NOT_EQ_SYM th3) else th3
  in  EQT_INTRO (CONV_RULE (C ALPHA tm) th4)
 )) with Failure _ -> failwith "VAR_NOT_EQ_STRUCT_OF_VAR_CONV";
; (*----------------------------------------------------------------------------*) (* CONJS_CONV : conv -> conv *) (* *) (* Written by T.F.Melham. *) (* Modified by R.J.Boulton. *) (* *) (* Apply a given conversion to a sequence of conjuncts. *) (* *) (* * need to check T case *) (* * need to flatten conjuncts on RHS *) (*----------------------------------------------------------------------------*) let CONJS_CONV = try( let is st th = try(fst(dest_const(rand(concl th))) = st) with Failure _ -> false in let v1 = genvar `:bool` and v2 = genvar `:bool` in let fthm1 = let th1 = ASSUME (mk_eq(v1,`F`)) in let cnj = mk_conj(v1,v2) in let th1 = DISCH cnj (EQ_MP th1 (CONJUNCT1 (ASSUME cnj))) in let th2 = DISCH `F` (CONTR cnj (ASSUME `F`)) in DISCH (mk_eq(v1,`F`)) (IMP_ANTISYM_RULE th1 th2) in let fthm2 = CONV_RULE(ONCE_DEPTH_CONV(REWR_CONV CONJ_SYM)) fthm1 in let fandr th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] fthm1) th in let fandl th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] fthm2) th in let tthm1 = let th1 = ASSUME (mk_eq(v1,`T`)) in let th2 = SUBS_OCCS [[2],th1] (REFL (mk_conj(v1,v2))) in DISCH (mk_eq(v1,`T`)) (ONCE_REWRITE_RULE [] th2) in let tthm2 = CONV_RULE(ONCE_DEPTH_CONV(REWR_CONV CONJ_SYM)) tthm1 in let tandr th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] tthm1) th in let tandl th tm = MP (INST [(lhs(concl th),v1);(tm,v2)] tthm2) th in let rec cconv conv tm = (let (c,cs) = dest_conj tm in let cth = conv c in if (is "F" cth) then fandr cth cs else let csth = cconv conv cs in if (is "F" csth) then fandl csth c else if (is "T" cth) then TRANS (tandr cth cs) csth else if (is "T" csth) then TRANS (tandl csth c) cth else try (MK_COMB((AP_TERM `(/\)` cth),csth)) with Failure _ -> conv tm ) in fun conv tm -> cconv conv tm) with Failure _ -> failwith "CONJS_CONV";; (*----------------------------------------------------------------------------*) (* ONE_STEP_RECTY_EQ_CONV : (thm # thm list # thm list) -> conv -> conv *) (* *) (* Single step conversion for equality between structures of a single *) (* recursive type. *) (* *) (* Based on code written by T.F.Melham. *) (* *) (* The theorems taken as arguments are the induction, distinctness and *) (* injectivity theorems for the recursive type, as proved by the functions: *) (* *) (* prove_induction_thm *) (* prove_constructors_distinct *) (* prove_constructors_one_one *) (* *) (* Since the latter two functions may fail, the distinctness and injectivity *) (* theorems are passed around as lists of conjuncts. *) (* *) (* If one side of the equation is a variable and that variable appears in the *) (* other side (nested in a structure) the equation is proved false. *) (* *) (* If the top-level constructors on the two sides of the equation are *) (* distinct the equation is proved false. *) (* *) (* If the top-level constructors on the two sides of the equation are the *) (* same a conjunction of equations is generated, one equation for each *) (* argument of the constructor. The conversion given as argument is then *) (* applied to each conjunct. If any of the applications of this conversion *) (* fail, so will the entire call. *) (* *) (* In other conditions the function fails. *) (*----------------------------------------------------------------------------*) (* Taken from HOL90 *) let ONE_STEP_RECTY_EQ_CONV (induction,distincts,oneOnes) = let NOT_EQ_CONV = EQF_INTRO o EQT_ELIM o (VAR_NOT_EQ_STRUCT_OF_VAR_CONV (induction,distincts,oneOnes)) o mk_neg in let INJ_REW = GEN_REWRITE_CONV I oneOnes (* Deleted empty_rewrites - GEN_REWRITE_CONV different in hol light - hope it works *) in let ths1 = map SPEC_ALL distincts in let ths2 = map (GEN_ALL o EQF_INTRO o NOT_EQ_SYM) ths1 in let dths = ths2 @ (map (GEN_ALL o EQF_INTRO) ths1) in let DIST_REW = GEN_REWRITE_CONV I dths in fun conv -> NOT_EQ_CONV ORELSEC DIST_REW ORELSEC (INJ_REW THENC (CONJS_CONV conv)) ORELSEC (fun tm -> failwith "ONE_STEP_RECTY_EQ_CONV") (*----------------------------------------------------------------------------*) (* RECTY_EQ_CONV : (thm # thm list # thm list) -> conv *) (* *) (* Function to simplify as far as possible an equation between two structures *) (* of some type, the type being specified by the triple of theorems. The *) (* structures may involve variables. The result may be a conjunction of *) (* equations simpler than the original. *) (*----------------------------------------------------------------------------*) let RECTY_EQ_CONV (induction,distincts,oneOnes) = try ( let one_step_conv = ONE_STEP_RECTY_EQ_CONV (induction,distincts,oneOnes) and REFL_CONV tm = let (l,r) = dest_eq tm in if (l = r) then EQT_INTRO (REFL l) else failwith "REFL_CONV" in let rec conv tm = (one_step_conv conv ORELSEC REFL_CONV ORELSEC ALL_CONV) tm in fun tm -> conv tm ) with Failure _ -> failwith "RECTY_EQ_CONV";;