(* ========================================================================= *)
(* Grobner basis algorithm.                                                  *)
(* ========================================================================= *)

needs "Complex/complex.ml";;
needs "Complex/quelim.ml";;

prioritize_complex();;

(* ------------------------------------------------------------------------- *)
(* Utility functions.                                                        *)
(* ------------------------------------------------------------------------- *)

let allpairs f l1 l2 =
  itlist ((@) o C map l2 o f) l1 [];;

let rec merge ord l1 l2 =
  match l1 with
    [] -> l2
  | h1::t1 -> match l2 with
                [] -> l1
              | h2::t2 -> if ord h1 h2 then h1::(merge ord t1 l2)
                          else h2::(merge ord l1 t2);;

let sort ord =
  let rec mergepairs l1 l2 =
    match (l1,l2) with
        ([s],[]) -> s
      | (l,[]) -> mergepairs [] l
      | (l,[s1]) -> mergepairs (s1::l) []
      | (l,(s1::s2::ss)) -> mergepairs ((merge ord s1 s2)::l) ss in
  fun l -> if l = [] then [] else mergepairs [] (map (fun x -> [x]) l);;

(* ------------------------------------------------------------------------- *)
(* Type for recording history, i.e. how a polynomial was obtained.           *)
(* ------------------------------------------------------------------------- *)

type history =
   Start of int
 | Mmul of (num * (int list)) * history
 | Add of history * history;;

(* ------------------------------------------------------------------------- *)
(* Conversion of leaves, i.e. variables and constant rational constants.     *)
(* ------------------------------------------------------------------------- *)

let grob_var vars tm =
  let res = map (fun i -> if i = tm then 1 else 0) vars in
  if exists (fun x -> x <> 0) res then [Int 1,res] else failwith "grob_var";;

let grob_const =
  let cx_tm = `Cx` in
  fun vars tm ->
    try let l,r = dest_comb tm in
        if l = cx_tm then
          let x = rat_of_term r in
          if x =/ Int 0 then [] else [x,map (fun v -> 0) vars]
        else failwith ""
    with Failure _ -> failwith "grob_const";;

(* ------------------------------------------------------------------------- *)
(* Monomial ordering.                                                        *)
(* ------------------------------------------------------------------------- *)

let morder_lt =
  let rec lexorder l1 l2 =
    match (l1,l2) with
        [],[] -> false
      | (x1::o1,x2::o2) -> x1 > x2 or x1 = x2 & lexorder o1 o2
      | _ -> failwith "morder: inconsistent monomial lengths" in
  fun m1 m2 -> let n1 = itlist (+) m1 0
               and n2 = itlist (+) m2 0 in
               n1 < n2 or n1 = n2 & lexorder m1 m2;;

let morder_le m1 m2 = morder_lt m1 m2 or (m1 = m2);;

let morder_gt m1 m2 = morder_lt m2 m1;;

(* ------------------------------------------------------------------------- *)
(* Arithmetic on canonical polynomials.                                      *)
(* ------------------------------------------------------------------------- *)

let grob_neg = map (fun (c,m) -> (minus_num c,m));;

let rec grob_add l1 l2 =
  match (l1,l2) with
    ([],l2) -> l2
  | (l1,[]) -> l1
  | ((c1,m1)::o1,(c2,m2)::o2) ->
        if m1 = m2 then
          let c = c1+/c2 and rest = grob_add o1 o2 in
          if c =/ Int 0 then rest else (c,m1)::rest
        else if morder_lt m2 m1 then (c1,m1)::(grob_add o1 l2)
        else (c2,m2)::(grob_add l1 o2);;

let grob_sub l1 l2 = grob_add l1 (grob_neg l2);;

let grob_mmul (c1,m1) (c2,m2) = (c1*/c2,map2 (+) m1 m2);;

let rec grob_cmul cm pol = map (grob_mmul cm) pol;;

let rec grob_mul l1 l2 =
  match l1 with
    [] -> []
  | (h1::t1) -> grob_add (grob_cmul h1 l2) (grob_mul t1 l2);;

let rec grob_pow vars l n =
  if n < 0 then failwith "grob_pow: negative power"
  else if n = 0 then [Int 1,map (fun v -> 0) vars]
  else grob_mul l (grob_pow vars l (n - 1));;

(* ------------------------------------------------------------------------- *)
(* Monomial division operation.                                              *)
(* ------------------------------------------------------------------------- *)

let mdiv (c1,m1) (c2,m2) =
  (c1//c2,
   map2 (fun n1 n2 -> if n1 < n2 then failwith "mdiv" else n1-n2) m1 m2);;

(* ------------------------------------------------------------------------- *)
(* Lowest common multiple of two monomials.                                  *)
(* ------------------------------------------------------------------------- *)

let mlcm (c1,m1) (c2,m2) = (Int 1,map2 max m1 m2);;

(* ------------------------------------------------------------------------- *)
(* Reduce monomial cm by polynomial pol, returning replacement for cm.       *)
(* ------------------------------------------------------------------------- *)

let reduce1 cm (pol,hpol) =
  match pol with
    [] -> failwith "reduce1"
  | cm1::cms -> try let (c,m) = mdiv cm cm1 in
                    (grob_cmul (minus_num c,m) cms,
                     Mmul((minus_num c,m),hpol))
                with Failure _ -> failwith "reduce1";;

(* ------------------------------------------------------------------------- *)
(* Try this for all polynomials in a basis.                                  *)
(* ------------------------------------------------------------------------- *)

let reduceb cm basis = tryfind (fun p -> reduce1 cm p) basis;;

(* ------------------------------------------------------------------------- *)
(* Reduction of a polynomial (always picking largest monomial possible).     *)
(* ------------------------------------------------------------------------- *)

let rec reduce basis (pol,hist) =
  match pol with
    [] -> (pol,hist)
  | cm::ptl -> try let q,hnew = reduceb cm basis in
                   reduce basis (grob_add q ptl,Add(hnew,hist))
               with Failure _ ->
                   let q,hist' = reduce basis (ptl,hist) in
                   cm::q,hist';;

(* ------------------------------------------------------------------------- *)
(* Check for orthogonality w.r.t. LCM.                                       *)
(* ------------------------------------------------------------------------- *)

let orthogonal l p1 p2 =
  snd l = snd(grob_mmul (hd p1) (hd p2));;

(* ------------------------------------------------------------------------- *)
(* Compute S-polynomial of two polynomials.                                  *)
(* ------------------------------------------------------------------------- *)

let spoly cm ph1 ph2 =
  match (ph1,ph2) with
    ([],h),p -> ([],h)
  | p,([],h) -> ([],h)
  | (cm1::ptl1,his1),(cm2::ptl2,his2) ->
        (grob_sub (grob_cmul (mdiv cm cm1) ptl1)
                  (grob_cmul (mdiv cm cm2) ptl2),
         Add(Mmul(mdiv cm cm1,his1),
             Mmul(mdiv (minus_num(fst cm),snd cm) cm2,his2)));;

(* ------------------------------------------------------------------------- *)
(* Make a polynomial monic.                                                  *)
(* ------------------------------------------------------------------------- *)

let monic (pol,hist) =
  if pol = [] then (pol,hist) else
  let c',m' = hd pol in
  (map (fun (c,m) -> (c//c',m)) pol,
   Mmul((Int 1 // c',map (K 0) m'),hist));;

(* ------------------------------------------------------------------------- *)
(* The most popular heuristic is to order critical pairs by LCM monomial.    *)
(* ------------------------------------------------------------------------- *)

let forder ((c1,m1),_) ((c2,m2),_) = morder_lt m1 m2;;

(* ------------------------------------------------------------------------- *)
(* Stupid stuff forced on us by lack of equality test on num type.           *)
(* ------------------------------------------------------------------------- *)

let rec poly_lt p q =
  match (p,q) with
    p,[] -> false
  | [],q -> true
  | (c1,m1)::o1,(c2,m2)::o2 ->
        c1 </ c2 or
        c1 =/ c2 & (m1 < m2 or m1 = m2 & poly_lt o1 o2);;

let align ((p,hp),(q,hq)) =
  if poly_lt p q then ((p,hp),(q,hq)) else ((q,hq),(p,hp));;

let poly_eq p1 p2 =
  forall2 (fun (c1,m1) (c2,m2) -> c1 =/ c2 & m1 = m2) p1 p2;;

let memx ((p1,h1),(p2,h2)) ppairs =
  not (exists (fun ((q1,_),(q2,_)) -> poly_eq p1 q1 & poly_eq p2 q2) ppairs);;

(* ------------------------------------------------------------------------- *)
(* Buchberger's second criterion.                                            *)
(* ------------------------------------------------------------------------- *)

let criterion2 basis (lcm,((p1,h1),(p2,h2))) opairs =
  exists (fun g -> not(poly_eq (fst g) p1) & not(poly_eq (fst g) p2) &
                   can (mdiv lcm) (hd(fst g)) &
                   not(memx (align(g,(p1,h1))) (map snd opairs)) &
                   not(memx (align(g,(p2,h2))) (map snd opairs))) basis;;

(* ------------------------------------------------------------------------- *)
(* Test for hitting constant polynomial.                                     *)
(* ------------------------------------------------------------------------- *)

let constant_poly p =
  length p = 1 & forall ((=) 0) (snd(hd p));;

(* ------------------------------------------------------------------------- *)
(* Grobner basis algorithm.                                                  *)
(* ------------------------------------------------------------------------- *)

let rec grobner histories basis pairs =
  print_string(string_of_int(length basis)^" basis elements and "^
               string_of_int(length pairs)^" critical pairs");
  print_newline();
  match pairs with
    [] -> rev histories,basis
  | (l,(p1,p2))::opairs ->
        let (sp,hist) = monic (reduce basis (spoly l p1 p2)) in
        if sp = [] or criterion2 basis (l,(p1,p2)) opairs
        then grobner histories basis opairs else
        let sph = sp,Start(length histories) in
        if constant_poly sp
        then grobner ((sp,hist)::histories) (sph::basis) [] else
        let rawcps =
          map (fun p -> mlcm (hd(fst p)) (hd sp),align(p,sph)) basis in
        let newcps = filter
          (fun (l,(p,q)) -> not(orthogonal l (fst p) (fst q))) rawcps in
        grobner ((sp,hist)::histories) (sph::basis)
                (merge forder opairs (sort forder newcps));;

(* ------------------------------------------------------------------------- *)
(* Overall function.                                                         *)
(* ------------------------------------------------------------------------- *)

let groebner pols =
  let npols = map2 (fun p n -> p,Start n) pols (0--(length pols - 1)) in
  let phists = filter (fun (p,_) -> p <> []) npols in
  let bas0 = map monic phists in
  let bas = map2 (fun (p,h) n -> p,Start n) bas0
                 ((length bas0)--(2 * length bas0 - 1)) in
  let prs0 = allpairs (fun x y -> x,y) bas bas in
  let prs1 = filter (fun ((x,_),(y,_)) -> poly_lt x y) prs0 in
  let prs2 = map (fun (p,q) -> mlcm (hd(fst p)) (hd(fst q)),(p,q)) prs1 in
  let prs3 = filter (fun (l,(p,q)) -> not(orthogonal l (fst p) (fst q))) prs2 in
  grobner (rev bas0 @ rev phists) bas (mergesort forder prs3);;

(* ------------------------------------------------------------------------- *)
(* Alternative orthography.                                                  *)
(* ------------------------------------------------------------------------- *)

let gr'o'bner = groebner;;

(* ------------------------------------------------------------------------- *)
(* Conversion from HOL term.                                                 *)
(* ------------------------------------------------------------------------- *)

let grobify_term =
  let neg_tm = `(--):complex->complex`
  and add_tm = `(+):complex->complex->complex`
  and sub_tm = `(-):complex->complex->complex`
  and mul_tm = `(*):complex->complex->complex`
  and pow_tm = `(pow):complex->num->complex` in
  let rec grobify_term vars tm =
    try grob_var vars tm with Failure _ ->
    try grob_const vars tm with Failure _ ->
    let lop,r = dest_comb tm in
    if lop = neg_tm then grob_neg(grobify_term vars r) else
    let op,l = dest_comb lop in
    if op = pow_tm then
      grob_pow vars (grobify_term vars l) (dest_small_numeral r)
    else
     (if op = add_tm then grob_add else if op = sub_tm then grob_sub
      else if op = mul_tm then grob_mul else failwith "unknown term")
     (grobify_term vars l) (grobify_term vars r) in
  fun vars tm ->
    try grobify_term vars tm with Failure _ -> failwith "grobify_term";;

let grobvars =
  let neg_tm = `(--):complex->complex`
  and add_tm = `(+):complex->complex->complex`
  and sub_tm = `(-):complex->complex->complex`
  and mul_tm = `(*):complex->complex->complex`
  and pow_tm = `(pow):complex->num->complex` in
  let rec grobvars tm acc =
    if is_complex_const tm then acc
    else if not (is_comb tm) then tm::acc else
    let lop,r = dest_comb tm in
    if lop = neg_tm then grobvars r acc
    else if not (is_comb lop) then tm::acc else
    let op,l = dest_comb lop in
    if op = pow_tm then grobvars l acc
    else if op = mul_tm or op = sub_tm or op = add_tm
    then grobvars l (grobvars r acc)
    else tm::acc in
  fun tm -> setify(grobvars tm []);;

let grobify_equations =
  let zero_tm = `Cx(&0)`
  and sub_tm = `(-):complex->complex->complex`
  and complex_ty = `:complex` in
  let grobify_equation vars tm =
  let l,r = dest_eq tm in
    if r <> zero_tm then grobify_term vars (mk_comb(mk_comb(sub_tm,l),r))
    else grobify_term vars l in
  fun tm ->
    let cjs = conjuncts tm in
    let rawvars = itlist
       (fun eq acc -> let l,r = dest_eq eq in
                     union (union (grobvars l) (grobvars r)) acc) cjs [] in
    let vars = sort (fun x y -> x < y) rawvars in
    vars,map(grobify_equation vars) cjs;;

(* ------------------------------------------------------------------------- *)
(* Printer.                                                                  *)
(* ------------------------------------------------------------------------- *)

let string_of_monomial vars (c,m) =
  let xns = filter (fun (x,y) -> y <> 0) (map2 (fun x y -> x,y) vars m) in
  let xnstrs = map
    (fun (x,n) -> x^(if n = 1 then "" else "^"^(string_of_int n))) xns in
  if xns = [] then Num.string_of_num c else
  let basstr = if c =/ Int 1 then "" else (Num.string_of_num c)^" * " in
  basstr ^ end_itlist (fun s t -> s^" * "^t) xnstrs;;

let string_of_polynomial vars l =
  if l = [] then "0" else
  end_itlist (fun s t -> s^" + "^t) (map (string_of_monomial vars) l);;

(* ------------------------------------------------------------------------- *)
(* Resolve a proof.                                                          *)
(* ------------------------------------------------------------------------- *)

let rec resolve_proof vars prf =
  match prf with
    Start n ->
        [n,[Int 1,map (K 0) vars]]
  | Mmul(pol,lin) ->
        let lis = resolve_proof vars lin in
        map (fun (n,p) -> n,grob_cmul pol p) lis
  | Add(lin1,lin2) ->
        let lis1 = resolve_proof vars lin1
        and lis2 = resolve_proof vars lin2 in
        let dom = setify(union (map fst lis1) (map fst lis2)) in
        map (fun n -> let a = try assoc n lis1 with Failure _ -> []
                      and b = try assoc n lis2 with Failure _ -> [] in
                      n,grob_add a b) dom;;

(* ------------------------------------------------------------------------- *)
(* Convert a polynomial back to HOL.                                         *)
(* ------------------------------------------------------------------------- *)

let holify_polynomial =
  let complex_ty = `:complex`
  and pow_tm = `(pow):complex->num->complex`
  and mk_mul = mk_binop `(*):complex->complex->complex`
  and mk_add = mk_binop `(+):complex->complex->complex`
  and zero_tm = `Cx(&0)`
  and add_tm = `(+):complex->complex->complex`
  and mul_tm = `(*):complex->complex->complex`
  and complex_term_of_rat = curry mk_comb `Cx` o term_of_rat in
  let holify_varpow (v,n) =
    if n = 1 then v
    else list_mk_comb(pow_tm,[v;mk_small_numeral n]) in
  let holify_monomial vars (c,m) =
    let xps = map holify_varpow (filter (fun (_,n) -> n <> 0) (zip vars m)) in
    end_itlist mk_mul (complex_term_of_rat c :: xps) in
  let holify_polynomial vars p =
    if p = [] then zero_tm
    else end_itlist mk_add (map (holify_monomial vars) p) in
  holify_polynomial;;

(* ------------------------------------------------------------------------- *)
(* Recursively find the set of basis elements involved.                      *)
(* ------------------------------------------------------------------------- *)

let dependencies =
  let rec dependencies prf acc =
    match prf with
      Start n -> n::acc
    | Mmul(pol,lin) -> dependencies lin acc
    | Add(lin1,lin2) -> dependencies lin1 (dependencies lin2 acc) in
  fun prf -> setify(dependencies prf []);;

let rec involved deps sofar todo =
  match todo with
    [] -> sort (<) sofar
  | (h::hs) ->
        if mem h sofar then involved deps sofar hs
        else involved deps (h::sofar) (el h deps @ hs);;

(* ------------------------------------------------------------------------- *)
(* Refute a conjunction of equations in HOL.                                 *)
(* ------------------------------------------------------------------------- *)

let GROBNER_REFUTE =
  let add_tm = `(+):complex->complex->complex`
  and mul_tm = `(*):complex->complex->complex` in
  let APPLY_pth = MATCH_MP(SIMPLE_COMPLEX_ARITH
    `(x = y) ==> (x + Cx(--(&1)) * (y + Cx(&1)) = Cx(&0)) ==> F`)
  and MK_ADD th1 th2 = MK_COMB(AP_TERM add_tm th1,th2) in
  let rec holify_lincombs vars cjs prfs =
    match prfs with
      [] -> cjs
    | (p::ps) ->
        if p = [] then holify_lincombs vars (cjs @ [TRUTH]) ps else
        let holis = map (fun (n,q) -> n,holify_polynomial vars q) p in
        let ths =
          map (fun (n,m) -> AP_TERM (mk_comb(mul_tm,m)) (el n cjs)) holis in
        let th = CONV_RULE(BINOP_CONV COMPLEX_POLY_CONV)
                          (end_itlist MK_ADD ths) in
        holify_lincombs vars (cjs @ [th]) ps in
  fun tm ->
    let vars,pols = grobify_equations tm in
    let (prfs,gb) = groebner pols in
    let (_,prf) =
      find (fun (p,h) -> length p = 1 & forall ((=)0) (snd(hd p))) gb in
    let deps = map (dependencies o snd) prfs
    and depl = dependencies prf in
    let need = involved deps [] depl in
    let pprfs =
      map2 (fun p n -> if mem n need then resolve_proof vars (snd p) else [])
           prfs (0--(length prfs - 1))
    and ppr = resolve_proof vars prf in
    let ths = CONJUNCTS(ASSUME tm) in
    let th = last
     (holify_lincombs vars ths (snd(chop_list(length ths) (pprfs @ [ppr])))) in
    CONV_RULE COMPLEX_RAT_EQ_CONV th;;

(* ------------------------------------------------------------------------- *)
(* Overall conversion.                                                       *)
(* ------------------------------------------------------------------------- *)

let COMPLEX_ARITH =
  let pth0 = SIMPLE_COMPLEX_ARITH `(x = y) <=> (x - y = Cx(&0))`
  and pth1 = prove
   (`!x. ~(x = Cx(&0)) <=> ?z. z * x + Cx(&1) = Cx(&0)`,
    CONV_TAC(time FULL_COMPLEX_QUELIM_CONV))
  and pth2a = prove
   (`!x y u v. ~(x = y) \/ ~(u = v) <=>
                ?w z. w * (x - y) + z * (u - v) + Cx(&1) = Cx(&0)`,
    CONV_TAC(time FULL_COMPLEX_QUELIM_CONV))
  and pth2b = prove
   (`!x y. ~(x = y) <=> ?z. z * (x - y) + Cx(&1) = Cx(&0)`,
    CONV_TAC(time FULL_COMPLEX_QUELIM_CONV))
  and pth3 = TAUT `(p ==> F) ==> (~q <=> p) ==> q` in
  let GEN_PRENEX_CONV =
    GEN_REWRITE_CONV REDEPTH_CONV
     [AND_FORALL_THM;
      LEFT_AND_FORALL_THM;
      RIGHT_AND_FORALL_THM;
      LEFT_OR_FORALL_THM;
      RIGHT_OR_FORALL_THM;
      OR_EXISTS_THM;
      LEFT_OR_EXISTS_THM;
      RIGHT_OR_EXISTS_THM;
      LEFT_AND_EXISTS_THM;
      RIGHT_AND_EXISTS_THM] in
  let INITIAL_CONV =
    NNF_CONV THENC
    GEN_REWRITE_CONV ONCE_DEPTH_CONV [pth1] THENC
    GEN_REWRITE_CONV ONCE_DEPTH_CONV [pth2a] THENC
    GEN_REWRITE_CONV ONCE_DEPTH_CONV [pth2b] THENC
    ONCE_DEPTH_CONV(GEN_REWRITE_CONV I [pth0] o
                    check ((<>) `Cx(&0)` o rand)) THENC
    GEN_PRENEX_CONV THENC
    DNF_CONV in
  fun tm ->
    let avs = frees tm in
    let tm' = list_mk_forall(avs,tm) in
    let th1 = INITIAL_CONV(mk_neg tm') in
    let evs,bod = strip_exists(rand(concl th1)) in
    if is_forall bod then failwith "COMPLEX_ARITH: non-universal formula" else
    let djs = disjuncts bod in
    let th2 = end_itlist SIMPLE_DISJ_CASES(map GROBNER_REFUTE djs) in
    let th3 = itlist SIMPLE_CHOOSE evs th2 in
    SPECL avs (MATCH_MP (MATCH_MP pth3 (DISCH_ALL th3)) th1);;
