
module Sem

  use Choice.Choice
  use Functions.Func
  use Firstorder_symbol_spec.Spec
  use Firstorder_term_spec.Spec
  use Firstorder_formula_spec.Spec
  use Firstorder_formula_list_spec.Spec
  use Firstorder_tableau_spec.Spec
  use bool.Bool
  use list.List
  use option.Option
  use OptionFuncs.Funcs

  type model 'ls 'st = {
    interp_fun : 'ls -> (list 'st -> 'st) ;
    interp_pred : 'ls -> (list 'st -> bool) ;
  }

  function term_semantic (t:fo_term 'ls 'b) (m:model 'ls 'st)
    (rho:'b -> 'st) : 'st = match t with
      | Var_fo_term x -> rho x
      | App (Var_symbol f) l -> let ifun = m.interp_fun in
        ifun f (term_list_semantic l m rho)
    end

  with term_list_semantic (t:fo_term_list 'ls 'b) (m:model 'ls 'st)
    (rho:'b -> 'st) : list 'st = match t with
      | FONil -> Nil
      | FOCons x q -> Cons (term_semantic x m rho) (term_list_semantic q m rho)
    end

  predicate formula_semantic (t:fo_formula 'ls 'b) (m:model 'ls 'st)
    (rho:'b -> 'st) = match t with
      | Forall t -> forall x:'st.
        formula_semantic t m (ocase rho x)
      | Exists t -> exists x:'st.
        formula_semantic t m (ocase rho x)
      | And t1 t2 -> formula_semantic t1 m rho /\ formula_semantic t2 m rho
      | Or t1 t2 -> formula_semantic t1 m rho \/ formula_semantic t2 m rho
      | Not t -> not (formula_semantic t m rho)
      | FTrue -> true
      | FFalse -> false
      | PApp (Var_symbol p) l -> let ipred = m.interp_pred in
        ipred p (term_list_semantic l m rho)
    end

  predicate formula_list_conj_semantic (t:fo_formula_list 'ls 'b)
    (m:model 'ls 'st) (rho:'b -> 'st) = match t with
      | FOFNil -> true
      | FOFCons x q -> formula_semantic x m rho /\
        formula_list_conj_semantic q m rho
    end

  predicate formula_list_disj_semantic (t:fo_formula_list 'ls 'b)
    (m:model 'ls 'st) (rho:'b -> 'st) = match t with
      | FOFNil -> false
      | FOFCons x q -> formula_semantic x m rho \/
        formula_list_disj_semantic q m rho
    end

  predicate tableau_node (b:bool) (phib:fo_formula_list 'ls 'b)
    (phi0:fo_formula 'ls 'b) (m:model 'ls 'st) (rho:'b -> 'st) =
    ( b = True /\ formula_semantic phi0 m rho)
      \/ formula_list_disj_semantic phib m rho

  (* This one work by accumulation, as it is related to a context. *)
  predicate tableau_semantic_with (t:tableau 'ls 'b)
    (b:bool) (m:model 'ls 'st) (rho:'b -> 'st) =
    match t with
      | Root -> b = True
      | Node tnext phi0 phib ->
        let b' = if tableau_node b phib phi0 m rho
          then True
          else False in
        tableau_semantic_with tnext b' m rho
    end

  (* Abstraction-definition axiom :
     function semantic_subst (s:'b -> (fo_term 'ls 'c))
       (m:model 'ls 'st) (rho:'c -> 'st) : 'b -> 'st =
         (\ x:'b. term_semantic (s x) m rho) *)
  function semantic_subst (s:'b -> (fo_term 'ls 'c))
    (m:model 'ls 'st) (rho:'c -> 'st) : 'b -> 'st
  axiom semantic_subst_def : forall s:'b -> (fo_term 'ls 'c),
    m:model 'ls 'st, rho:'c -> 'st, x:'b.
      semantic_subst s m rho x = term_semantic (s x) m rho

  (*(* Abstraction-definition axiom :
     constant symbol_name : (symbol 'b) -> 'b =
       (\ x:symbol 'b. match x with Var_symbol x -> x end) *)
  constant symbol_name : (symbol 'b) -> 'b
  axiom symbol_name_def : forall x:symbol 'b.
    symbol_name x = match x with Var_symbol x -> x end
  let lemma symbol_name_subst_identity_inverse (u:unit) : unit
    ensures { rcompose symbol_name subst_id_symbol =
      (identity:(symbol 'b) -> (symbol 'b)) }
    ensures { rcompose subst_id_symbol symbol_name =
      (identity:'b -> 'b) }
  =
    assert { extensionalEqual (identity:(symbol 'b) -> (symbol 'b))
      (rcompose symbol_name subst_id_symbol) } ;
    assert { extensionalEqual (identity:'b -> 'b)
      (rcompose subst_id_symbol symbol_name) }*)

  function model_rename (r:'b -> 'c) (m:model 'c 'st) :
    model 'b 'st = {
    interp_fun = rcompose r m.interp_fun ;
    interp_pred = rcompose r m.interp_pred ;
  }

  lemma model_rename_id : forall m:model 'b 'st.
    model_rename identity m = m

  (* semantic commutation with substitution.
     Required for example for
     universal quantification elimination deduction rule:
     forall x. phi -> phi[x <- t] come from this lemma
     (*and generally speaking, quantifier handling*) *)

  let rec lemma term_semantic_subst_commutation (t:fo_term 'ls 'b)
    (m:model 'ls2 'st) (rho : 'c -> 'st)
    (thetal:'ls -> 'ls2)
    (theta:'b -> (fo_term 'ls2 'c)) : unit
    ensures { term_semantic (
      subst_fo_term t (rcompose thetal subst_id_symbol) theta) m rho =
      term_semantic t (model_rename thetal m) (semantic_subst theta m rho) }
    variant { size_fo_term t }
  =
    match t with
      | Var_fo_term x -> ()
      | App (Var_symbol f) l ->
        term_list_semantic_subst_commutation l m rho thetal theta ;
        let thetals = rcompose thetal subst_id_symbol in
        assert { subst_fo_term t thetals theta =
          App (Var_symbol (thetal f)) (subst_fo_term_list l thetals theta) } ;
        let m2 = model_rename thetal m in
        let l2 = term_list_semantic l m2 (semantic_subst theta m rho) in
        assert { eval m.interp_fun (thetal f) l2 =
            eval m2.interp_fun f l2 }
    end

  with lemma term_list_semantic_subst_commutation (t:fo_term_list 'ls 'b)
    (m:model 'ls2 'st) (rho : 'c -> 'st)
    (thetal:'ls -> 'ls2)
    (theta:'b -> (fo_term 'ls2 'c)) : unit
    ensures { term_list_semantic (
      subst_fo_term_list t (rcompose thetal subst_id_symbol) theta) m rho =
      term_list_semantic t (model_rename thetal m)
        (semantic_subst theta m rho) }
    variant { size_fo_term_list t }
  =
    match t with
      | FONil -> ()
      | FOCons x q -> term_semantic_subst_commutation x m rho thetal theta ;
        term_list_semantic_subst_commutation q m rho thetal theta
    end

  let lemma term_list_semantic_rename_commutation (t:fo_term_list 'ls 'b)
    (m:model 'ls2 'st) (rho:'c -> 'st)
    (thetal:'ls -> 'ls2) (theta:'b -> 'c) : unit
    ensures { term_list_semantic (
      rename_fo_term_list t thetal theta) m rho =
      term_list_semantic t (model_rename thetal m)
        (rcompose theta rho) }
  =
    let p1 = rcompose theta rho in
    let p2 = semantic_subst (rcompose theta subst_id_fo_term) m rho in
    assert { extensionalEqual p1 p2 && p1 = p2 }

  let lemma term_semantic_rename_commutation (t:fo_term 'ls 'b)
    (m:model 'ls2 'st) (rho:'c -> 'st)
    (thetal:'ls -> 'ls2) (theta:'b -> 'c) : unit
    ensures { term_semantic (rename_fo_term t thetal theta) m rho =
      term_semantic t (model_rename thetal m) (rcompose theta rho) }
  =
    assert { extensionalEqual (rcompose theta rho)
      (semantic_subst (subst_of_rename_fo_term theta) m rho) }


  let lemma semantic_lifting_commutation (theta:'b -> (fo_term 'ls 'c))
    (rho : 'c -> 'st) (m:model 'ls 'st) (x:'st) : unit
    ensures { semantic_subst (olifts_fo_term theta) m (ocase rho x) =
      ocase (semantic_subst theta m rho) x }
  =
    let stheta = olifts_fo_term theta in
    let srho = ocase rho x in
    let p1 = semantic_subst stheta m srho in
    let ctr = semantic_subst theta m rho in
    let p2 = ocase ctr x in
    assert { forall x:option 'b. match x with
      | None -> p1 None = p2 None
      | Some z -> p1 (Some z) = p2 (Some z) end
        && p1 x = p2 x } ;
    assert { extensionalEqual p1 p2 }

  let lemma formula_semantic_subst_commutation (t0:fo_formula 'ls 'b)
    (m0:model 'ls2 'st)
    (thetal0:'ls -> 'ls2)
    (theta0:'b -> (fo_term 'ls2 'c))
    (rho:'c -> 'st) : unit
    ensures { formula_semantic (
      subst_fo_formula t0 (rcompose thetal0 subst_id_symbol) theta0) m0 rho
      <-> formula_semantic t0
        (model_rename thetal0 m0) (semantic_subst theta0 m0 rho) }
  =
    let rec ghost aux (t:fo_formula 'ls3 'b2)
      (m:model 'ls4 'st)
      (thetal:'ls3 -> 'ls4)
      (theta:'b2 -> (fo_term 'ls4 'c2)) : unit
      ensures { forall rho:'c2 -> 'st.
        formula_semantic (
          subst_fo_formula t (rcompose thetal subst_id_symbol) theta) m rho
        <-> formula_semantic t
          (model_rename thetal m) (semantic_subst theta m rho) }
      variant { size_fo_formula t }
    =
      let thetals = rcompose thetal subst_id_symbol in
      match t with
        | Forall t2 -> let os = olifts_fo_term theta in
          let st = subst_fo_formula t thetals theta in
          let st2 = subst_fo_formula t2 thetals os in
          aux t2 m thetal os ;
          assert { st = Forall st2 }
        | Exists t2 -> let os = olifts_fo_term theta in
          let st = subst_fo_formula t thetals theta in
          let st2 = subst_fo_formula t2 thetals os in
          aux t2 m thetal os ;
          assert { st = Exists st2 }
        | And t1 t2 -> aux t1 m thetal theta ;
          aux t2 m thetal theta
        | Or t1 t2 -> aux t1 m thetal theta ;
          aux t2 m thetal theta
        | Not t2 -> aux t2 m thetal theta ;
          let st = subst_fo_formula t thetals theta in
          let st2 = subst_fo_formula t2 thetals theta in
          assert { st = Not st2 }
        | FTrue -> ()
        | FFalse -> ()
        | PApp (Var_symbol p) l ->
          let l2 = subst_fo_term_list l thetals theta in
          let t2 = subst_fo_formula t thetals theta in
          assert { t2 = PApp (Var_symbol (thetal p)) l2 } ;
          let m2 = model_rename thetal m in
          assert { forall rho:'c2 -> 'st.
            let rho2 = semantic_subst theta m rho in
            let l3 = term_list_semantic l m2 rho2 in
            let l4 = term_list_semantic l2 m rho in
            (l3 = l4 /\
             (formula_semantic t m2 rho2 <->
               eval m2.interp_pred p l3) /\
             (formula_semantic t2 m rho <->
               eval m.interp_pred (thetal p) l4)) &&
            (formula_semantic t m2 rho2 <->
             formula_semantic t2 m rho) }
      end in
    aux t0 m0 thetal0 theta0

  let lemma formula_semantic_rename_commutation
    (t:fo_formula 'ls 'b) (m:model 'ls2 'st)
    (thetal:'ls -> 'ls2)
    (theta:'b -> 'c) (rho:'c -> 'st) : unit
    ensures { formula_semantic (rename_fo_formula t thetal theta) m rho
      <-> formula_semantic t (model_rename thetal m) (rcompose theta rho) }
  =
    let thetas = rcompose theta subst_id_fo_term in
    assert { rename_fo_formula t thetal theta =
      subst_fo_formula t (rcompose thetal subst_id_symbol) thetas } ;
    let p1 = rcompose theta rho in let p2 = semantic_subst thetas m rho in
    assert { extensionalEqual p1 p2 && p1 = p2 }

  let lemma formula_semantic_term_subst_commutation
    (t:fo_formula 'ls 'b) (m:model 'ls 'st)
    (theta:'b -> (fo_term 'ls 'c)) (rho:'c -> 'st) : unit
    ensures {
      formula_semantic (subst_fo_formula t subst_id_symbol theta) m rho <->
      formula_semantic t m (semantic_subst theta m rho) }
  =
    let t2 = subst_fo_formula t (rcompose identity subst_id_symbol) theta in
    let rho2 = semantic_subst theta m rho in
    assert { formula_semantic t2 m rho <->
      formula_semantic t (model_rename identity m) rho2 }

  let lemma formula_semantic_term_rename_commutation
    (t:fo_formula 'ls 'b) (m:model 'ls 'st)
    (theta:'b -> 'c) (rho:'c -> 'st) : unit
    ensures { formula_semantic (rename_fo_formula t identity theta) m rho <->
      formula_semantic t m (rcompose theta rho) }
  =
    ()

  let rec lemma formula_list_conj_semantic_subst_commutation
    (t:fo_formula_list 'ls 'b) (m:model 'ls2 'st)
    (thetal:'ls -> 'ls2)
    (theta:'b -> (fo_term 'ls2 'c)) (rho:'c -> 'st) : unit
    ensures { formula_list_conj_semantic (
      subst_fo_formula_list t (rcompose thetal subst_id_symbol) theta) m rho
      <-> formula_list_conj_semantic t
        (model_rename thetal m) (semantic_subst theta m rho) }
    variant { size_fo_formula_list t }
  =
    match t with | FOFNil -> () | FOFCons _ q ->
      formula_list_conj_semantic_subst_commutation q m thetal theta rho end

  let rec lemma formula_list_disj_semantic_subst_commutation
    (t:fo_formula_list 'ls 'b) (m:model 'ls2 'st)
    (thetal:'ls -> 'ls2)
    (theta:'b -> (fo_term 'ls2 'c)) (rho:'c -> 'st) : unit
    ensures { formula_list_disj_semantic (
      subst_fo_formula_list t (rcompose thetal subst_id_symbol) theta) m rho
      <-> formula_list_disj_semantic t
        (model_rename thetal m) (semantic_subst theta m rho) }
    variant { size_fo_formula_list t }
  =
    match t with | FOFNil -> () | FOFCons _ q ->
      formula_list_disj_semantic_subst_commutation q m thetal theta rho end


  let lemma formula_list_conj_semantic_term_subst_commutation
    (t:fo_formula_list 'ls 'b) (m:model 'ls 'st)
    (theta:'b -> (fo_term 'ls 'c)) (rho:'c -> 'st) : unit
    ensures { formula_list_conj_semantic
        (subst_fo_formula_list t subst_id_symbol theta) m rho <->
      formula_list_conj_semantic t m (semantic_subst theta m rho) }
  =
    formula_list_conj_semantic_subst_commutation t m identity theta rho

  let lemma formula_list_disj_semantic_term_subst_commutation
    (t:fo_formula_list 'ls 'b) (m:model 'ls 'st)
    (theta:'b -> (fo_term 'ls 'c)) (rho:'c -> 'st) : unit
    ensures { formula_list_disj_semantic
        (subst_fo_formula_list t subst_id_symbol theta) m rho <->
      formula_list_disj_semantic t m (semantic_subst theta m rho) }
  =
    formula_list_disj_semantic_subst_commutation t m identity theta rho

  let rec lemma tableau_semantic_subst_commutation
    (t:tableau 'ls 'b) (m:model 'ls2 'st)
    (b:bool)
    (thetal:'ls -> 'ls2)
    (theta:'b -> (fo_term 'ls2 'c)) (rho:'c -> 'st) : unit
    ensures { tableau_semantic_with (
      subst_tableau t (rcompose thetal subst_id_symbol) theta) b m rho
      <-> tableau_semantic_with t b
        (model_rename thetal m) (semantic_subst theta m rho) }
    variant { size_tableau t }
  =
    match t with
      | Root -> ()
      | Node tnext phi0 phib ->
        let s1 = rcompose thetal subst_id_symbol in
        let b' = if tableau_node b
          (subst_fo_formula_list phib s1 theta)
          (subst_fo_formula phi0 s1 theta) m rho
          then True
          else False in
        tableau_semantic_subst_commutation tnext m b' thetal theta rho
    end

  let lemma tableau_semantic_term_subst_commutation
    (t:tableau 'ls 'b) (b:bool) (m:model 'ls 'st)
    (theta:'b -> (fo_term 'ls 'c)) (rho:'c -> 'st) : unit
    ensures { tableau_semantic_with (
      subst_tableau t subst_id_symbol theta) b m rho <->
      tableau_semantic_with t b m (semantic_subst theta m rho) }
  =
    tableau_semantic_subst_commutation t m b identity theta rho

  let rec lemma term_semantic_depend_only_free_var
    (t:fo_term 'ls 'b) (m1 m2:model 'ls 'st)
    (rho1 rho2:'b -> 'st) : unit
    requires { forall f:'ls. is_symbol_free_var_in_fo_term f t ->
      eval m1.interp_fun f = eval m2.interp_fun f /\
      eval m1.interp_pred f = eval m2.interp_pred f }
    requires { forall x:'b. is_fo_term_free_var_in_fo_term x t ->
      rho1 x = rho2 x }
    ensures { term_semantic t m1 rho1 = term_semantic t m2 rho2 }
    variant { size_fo_term t }
  =
    match t with Var_fo_term x -> () | App (Var_symbol f) l ->
      term_list_semantic_depend_only_free_var l m1 m2 rho1 rho2 ;
      assert { is_symbol_free_var_in_fo_term f t } end

  with lemma term_list_semantic_depend_only_free_var
    (t:fo_term_list 'ls 'b) (m1 m2:model 'ls 'st)
    (rho1 rho2:'b -> 'st) : unit
    requires { forall f:'ls. is_symbol_free_var_in_fo_term_list f t ->
      eval m1.interp_fun f = eval m2.interp_fun f /\
      eval m1.interp_pred f = eval m2.interp_pred f }
    requires { forall x:'b. is_fo_term_free_var_in_fo_term_list x t ->
      rho1 x = rho2 x }
    ensures { term_list_semantic t m1 rho1 = term_list_semantic t m2 rho2 }
    variant { size_fo_term_list t }
  =
    match t with FONil -> () | FOCons x q ->
      term_semantic_depend_only_free_var x m1 m2 rho1 rho2 ;
      term_list_semantic_depend_only_free_var q m1 m2 rho1 rho2 end

  let lemma formula_semantic_depend_only_free_var
    (t:fo_formula 'ls0 'b0) (m1 m2:model 'ls0 'st0)
    (rho1 rho2:'b0 -> 'st0) : unit
    requires { forall f:'ls0. is_symbol_free_var_in_fo_formula f t ->
      eval m1.interp_fun f = eval m2.interp_fun f /\
      eval m1.interp_pred f = eval m2.interp_pred f }
    requires { forall x:'b0. is_fo_term_free_var_in_fo_formula x t ->
      rho1 x = rho2 x }
    ensures { formula_semantic t m1 rho1 <-> formula_semantic t m2 rho2 }
  =
    let rec aux (t:fo_formula 'ls 'b) (m1 m2:model 'ls 'st) : unit
      requires { forall f:'ls. is_symbol_free_var_in_fo_formula f t ->
        eval m1.interp_fun f = eval m2.interp_fun f /\
        eval m1.interp_pred f = eval m2.interp_pred f }
      ensures { forall rho1 rho2:'b -> 'st.
        (forall x:'b.
          is_fo_term_free_var_in_fo_formula x t -> rho1 x = rho2 x) ->
        formula_semantic t m1 rho1 <-> formula_semantic t m2 rho2 }
      variant { size_fo_formula t }
    =
      match t with
        | Forall t2 -> aux t2 m1 m2 ;
          assert { forall rho1 rho2:'b -> 'st.
            (forall x:'b.
              is_fo_term_free_var_in_fo_formula x t -> rho1 x = rho2 x) ->
            ((forall x:'st. formula_semantic t2 m1 (ocase rho1 x) <->
              formula_semantic t2 m2 (ocase rho2 x)) &&
            ((forall x:'st. formula_semantic t2 m1 (ocase rho1 x)) <->
              formula_semantic t m1 rho1) &&
            ((forall x:'st. formula_semantic t2 m1 (ocase rho2 x)) <->
              formula_semantic t m2 rho2)) }
        | Exists t2 -> aux t2 m1 m2 ;
          assert { forall rho1 rho2:'b -> 'st.
            (forall x:'b.
              is_fo_term_free_var_in_fo_formula x t -> rho1 x = rho2 x) ->
            ((forall x:'st. formula_semantic t2 m1 (ocase rho1 x) <->
              formula_semantic t2 m2 (ocase rho2 x)) &&
            ((exists x:'st. formula_semantic t2 m1 (ocase rho1 x)) <->
              formula_semantic t m1 rho1) &&
            ((exists x:'st. formula_semantic t2 m1 (ocase rho2 x)) <->
              formula_semantic t m2 rho2)) }
        | And t1 t2 -> aux t1 m1 m2 ; aux t2 m1 m2
        | Or t1 t2 -> aux t1 m1 m2 ; aux t2 m1 m2
        | Not t2 -> aux t2 m1 m2
        | FTrue -> ()
        | FFalse -> ()
        | PApp (Var_symbol p) l ->
          assert { is_symbol_free_var_in_fo_formula p t } ;
          assert { forall rho1 rho2:'b -> 'st.
            ((forall x:'b. is_fo_term_free_var_in_fo_formula x t ->
              rho1 x = rho2 x) ->
            term_list_semantic l m1 rho1 = term_list_semantic l m2 rho2) /\
            (formula_semantic t m1 rho1 <->
              eval m1.interp_pred p (term_list_semantic l m1 rho1)) /\
            (formula_semantic t m2 rho2 <->
              eval m2.interp_pred p (term_list_semantic l m2 rho2)) }
      end in
    aux t m1 m2

  let rec lemma formula_list_conj_semantic_depend_only_free_var
    (t:fo_formula_list 'ls 'b) (m1 m2:model 'ls 'st)
    (rho1 rho2:'b -> 'st) : unit
    requires { forall f:'ls. is_symbol_free_var_in_fo_formula_list f t ->
      eval m1.interp_fun f = eval m2.interp_fun f /\
      eval m1.interp_pred f = eval m2.interp_pred f }
    requires { forall x:'b. is_fo_term_free_var_in_fo_formula_list x t ->
      rho1 x = rho2 x }
    ensures { formula_list_conj_semantic t m1 rho1 <->
      formula_list_conj_semantic t m2 rho2 }
    variant { size_fo_formula_list t }
  =
    match t with FOFNil -> () | FOFCons x q ->
      formula_semantic_depend_only_free_var x m1 m2 rho1 rho2 ;
      formula_list_conj_semantic_depend_only_free_var q m1 m2 rho1 rho2 end

  let rec lemma formula_list_disj_semantic_depend_only_free_var
    (t:fo_formula_list 'ls 'b) (m1 m2:model 'ls 'st)
    (rho1 rho2:'b -> 'st) : unit
    requires { forall f:'ls. is_symbol_free_var_in_fo_formula_list f t ->
      eval m1.interp_fun f = eval m2.interp_fun f /\
      eval m1.interp_pred f = eval m2.interp_pred f }
    requires { forall x:'b. is_fo_term_free_var_in_fo_formula_list x t ->
      rho1 x = rho2 x }
    ensures { formula_list_disj_semantic t m1 rho1 <->
      formula_list_disj_semantic t m2 rho2 }
    variant { size_fo_formula_list t }
  =
    match t with FOFNil -> () | FOFCons x q ->
      formula_semantic_depend_only_free_var x m1 m2 rho1 rho2 ;
      formula_list_disj_semantic_depend_only_free_var q m1 m2 rho1 rho2 end

  predicate formula_list_mem (phi:fo_formula 'ls 'b)
    (l:fo_formula_list 'ls 'b) = match l with
      | FOFNil -> false | FOFCons x q -> x = phi \/ formula_list_mem phi q end

  let rec lemma formula_list_conj_semantic_other_def
    (l:fo_formula_list 'ls 'b) (m:model 'ls 'st)
    (rho:'b -> 'st) : unit
    ensures { formula_list_conj_semantic l m rho <->
      (forall phi:fo_formula 'ls 'b.
        formula_list_mem phi l -> formula_semantic phi m rho) }
    variant { size_fo_formula_list l }
  = match l with FOFNil -> () | FOFCons _ q ->
    formula_list_conj_semantic_other_def q m rho end

  let rec lemma formula_list_disj_semantic_other_def
    (l:fo_formula_list 'ls 'b) (m:model 'ls 'st)
    (rho:'b -> 'st) : unit
    ensures { formula_list_disj_semantic l m rho <->
      (exists phi:fo_formula 'ls 'b.
        formula_list_mem phi l /\ formula_semantic phi m rho) }
    variant { size_fo_formula_list l }
  = match l with FOFNil -> () | FOFCons x q ->
    formula_list_disj_semantic_other_def q m rho end

  (* Problem : validity/unsatifiability are not even axiomatizable in why3,
     since we would have to introduce it from a type quantification.
     However, we can define a demonstrability predicate and show for it
     the same elimination principle as unsatisfiability. *)

  (* Demonstration object (not directly a predicate,
     so we can actually construct the proof object ourselves). *)

  (* G |- A can be read as : for every model+interpretation,
     if /\G is true then A too.
     Which in particular mean : if G |- false, G have no model,
     so we can use G |- false instead of unsat predicate as long
     as it can represent all our demonstration steps. *)

  (*type demonstration 'ls 'b =
    | Axiom
    | ModusPonens (demonstration 'ls 'b) (demonstration 'ls 'b)
      (fo_formula 'ls 'b)
    | Abstraction (demonstration 'ls 'b) (fo_formula 'ls 'b)
      (fo_formula 'ls 'b)
    | ConjunctionIntro (demonstration 'ls 'b) (fo_formula 'ls 'b)
      (demonstration 'ls 'b) (fo_formula 'ls 'b)
    | ConjunctionLeft (demonstration 'ls 'b) (fo_formula 'ls 'b)
    | ConjunctionRight (demonstration 'ls 'b) (fo_formula 'ls 'b)
    | DisjunctionLeft (demonstration 'ls 'b) (fo_formula 'ls 'b)
      (fo_formula 'ls 'b)
    | DisjunctionRight (demonstration 'ls 'b) (fo_formula 'ls 'b)
      (fo_formula 'ls 'b)
    | DisjunctionElim (demonstration 'ls 'b) (demonstration 'ls 'b)
      (demonstration 'ls 'b) (fo_formula 'ls 'b) (fo_formula 'ls 'b)
    | UniversalInstantiation (demonstration 'ls 'b)
      (fo_formula 'ls (option 'b)) (fo_term 'ls 'b)
    | Instantiation (demonstration 'ls 'b)
      (fo_formula_list 'ls 'b)
      (fo_formula 'ls 'b) 'b (fo_term 'ls 'b)
    | ExistentialIntroduction (demonstration 'ls 'b)
      (fo_formula 'ls (option 'b)) (fo_term 'ls 'b)
    | ExistentialElimination (demonstration 'ls 'b)
      (demonstration 'ls 'b)
      (fo_formula 'ls (option 'b)) (fo_formula 'ls (option 'b))
    | PointlessExistential (demonstration 'ls 'b)
    | ExFalso (demonstration 'ls 'b)
    | Trivial
    | Weakening (demonstration 'ls 'b) (fo_formula_list 'ls 'b)
    | Skolemization (demonstration 'ls 'b) (fo_formula 'ls 'b) 'ls*)

  (*predicate is_skolem_axiom (phi:fo_formula 'ls 'b) (f:'ls)
    (env:fo_term_list 'ls 'b) =
    match phi with
      | Forall phi2 -> is_skolem_axiom phi2 f
        (FOCons (Var_fo_term None) (rename_fo_term_list env identity some))
      | Or (Not (Exists phi2)) phi3 ->
        phi3 = subst_fo_formula phi2 subst_id_symbol
          (ocase subst_id_fo_term (App (Var_symbol f) env)) /\
        not(is_symbol_free_var_in_fo_formula f phi2)
      | _ -> false
    end*)

  (*(* Hack to force possibility of instantiation of extend_env
     definition axiom ! *)
  function extend_env_selection (g:(list 'st) -> ('b -> 'st))
    (x:option 'b) (y:'st) (q:list 'st) : 'st = match x with
      | None -> y | Some x -> g q x end
  (* Abstraction-definition axiom :
     function extend_env (g:(list 'st) -> ('b -> 'st) :
       (list 'st) -> ((option 'b) -> 'st) =
       (\ l:list 'st. (\ x:option 'b. match l with
         | Cons y q -> extend_env_selection g x y q
         | Nil -> default ) ) *)
  function extend_env (g:(list 'st) -> ('b -> 'st)) :
    (list 'st) -> ((option 'b) -> 'st)
  axiom extend_env_def : forall g:(list 'st) -> ('b -> 'st),
    l:list 'st,x:option 'b. extend_env g l x =
    match l with
      | Cons y q -> extend_env_selection g x y q
      | Nil -> default
    end

  lemma extend_env_none : forall g:(list 'st) -> ('b -> 'st),
    y:'st,l:list 'st. extend_env g (Cons y l) None = y
  lemma extend_env_some : forall g:(list 'st) -> ('b -> 'st),
    x:'b,y:'st,l:list 'st. extend_env g (Cons y l) (Some x) = g l x*)

  (* Abstraction-definition axiom :
    function skolem_predicate (phi:fo_formula 'ls (option 'b))
      (m:model 'ls 'st) (rho:'b -> 'st) : 'st -> bool =
      (\ x:'st. formula_semantic phi m (ocase rho x) ) *)
  function skolem_predicate (phi:fo_formula 'ls (option 'b))
    (m:model 'ls 'st) (rho:'b -> 'st) : 'st -> bool
  axiom skolem_predicate_def : forall phi:fo_formula 'ls (option 'b),
    m:model 'ls 'st,rho:'b -> 'st,x:'st.
    skolem_predicate phi m rho x <-> formula_semantic phi m (ocase rho x)

  (* Abstraction-definition axiom :
    function skolem_function (phi:fo_formula 'ls (option 'b))
      (m:model 'ls 'st) (g:(list 'st) -> ('b -> 'st)) : (list 'st) -> 'st
      = (\ l:list 'st. choice (skolem_predicate phi m rho (g l)) ) *)
  function skolem_function (phi:fo_formula 'ls (option 'b))
    (m:model 'ls 'st) (g:(list 'st) -> ('b -> 'st)) : (list 'st) -> 'st
  axiom skolem_function_def : forall phi:fo_formula 'ls (option 'b),
    m:model 'ls 'st,g:(list 'st) -> ('b -> 'st),l:list 'st.
    skolem_function phi m g l = choice (skolem_predicate phi m (g l))

  (* Abstraction-definition axiom :
    function skolem_transformer (phi:fo_formula 'ls (option 'b)) (f:'ls)
      (g:(list 'st) -> ('b -> 'st)) : (model 'ls 'st) -> (model 'ls 'st) =
      (\ m:model 'ls 'st.
        {
          interp_fun = m.interp_fun[f <- skolem_function phi m g] ;
          interp_pred = m.interp_pred ;
        }) *)

  function skolem_transformer (phi:fo_formula 'ls (option 'b)) (f:'ls)
    (g:(list 'st) -> ('b -> 'st)) : (model 'ls 'st) -> (model 'ls 'st)
  axiom skolem_transformer_def : forall phi:fo_formula 'ls (option 'b),f:'ls,
    g:(list 'st) -> ('b -> 'st), m:model 'ls 'st.
    skolem_transformer phi f g m = {
      interp_fun = m.interp_fun[f <- skolem_function phi m g] ;
      interp_pred = m.interp_pred ;
    }

  let ghost skolem_model_transformer (phi:fo_formula 'ls (option 'b)) (f:'ls)
    (vars:fo_term_list 'ls 'b)
    (g:(list 'st) -> ('b -> 'st)) : (model 'ls 'st) -> (model 'ls 'st)
    requires { not(is_symbol_free_var_in_fo_formula f phi) }
    requires { forall m:model 'ls 'st, rho:'b -> 'st,x:'b.
      is_fo_term_free_var_in_fo_formula (Some x) phi ->
        g (term_list_semantic vars m rho) x = rho x }
    requires { not(is_symbol_free_var_in_fo_term_list f vars) }
    ensures { forall m:model 'ls 'st.
      m.interp_pred = (result m).interp_pred }
    ensures { forall m:model 'ls 'st,f0:'ls.
      f0 <> f -> eval m.interp_fun f0 = eval (result m).interp_fun f0 }
    ensures { forall m:model 'ls 'st,rho:'b -> 'st.
      formula_semantic (Exists phi) m rho ->
      formula_semantic (subst_fo_formula phi subst_id_symbol
        (ocase subst_id_fo_term (App (Var_symbol f) vars))) (result m) rho }
  =
    let skf = skolem_transformer phi f g in
    let skt = App (Var_symbol f) vars in
    let sks = ocase subst_id_fo_term skt in
    let phi' = subst_fo_formula phi subst_id_symbol sks in
    assert { forall m:model 'ls 'st,rho:'b -> 'st.
      let semf = skolem_function phi m g in
      let skm = skf m in
      let em = term_list_semantic vars m rho in
      let esm = term_list_semantic vars skm rho in
      (skm.interp_fun = m.interp_fun[f <- semf]) &&
      (skm.interp_pred = m.interp_pred) &&
      (em = esm) &&
      (forall x:'st.
        let rhox = ocase rho x in
        let rhox2 = ocase (g em) x in
        (forall y:option 'b.
          is_fo_term_free_var_in_fo_formula y phi ->
            rhox y = rhox2 y) &&
        (formula_semantic phi m rhox <-> formula_semantic phi m rhox2)) &&
      (let s1 = semantic_subst sks skm rho in
       let s2 = ocase rho (semf esm) in
       s1 None = s2 None &&
       (forall y:'b. s1 (Some y) = s2 (Some y)) &&
       extensionalEqual s1 s2 &&
       (forall f':'ls. is_symbol_free_var_in_fo_formula f' phi ->
          eval m.interp_fun f' = eval skm.interp_fun f' /\
          eval m.interp_pred f' = eval skm.interp_pred f') &&
       (forall x:'st.
          let rhox = ocase rho x in
          (formula_semantic phi m (ocase (g em) x) ->
           formula_semantic phi m (ocase (g em) (semf em)) &&
           formula_semantic phi m (ocase rho (semf em)) &&
           formula_semantic phi skm s2 &&
           formula_semantic phi' skm rho) &&
          (formula_semantic phi m rhox ->
           formula_semantic phi' skm rho))) &&
       (formula_semantic (Exists phi) m rho ->
         formula_semantic phi' skm rho)
    } ;
    skf

  (*
  let ghost skolemized_model (phi0:fo_formula 'ls0 'b0) (f0:'ls0)
    (m0:model 'ls0 'st) : model 'ls0 'st
    requires { is_skolem_axiom phi0 f0 FONil }
    requires { forall x:'b0. not(is_fo_term_free_var_in_fo_formula x phi0) }
    ensures { forall rho:'b0 -> 'st.
      formula_semantic phi0 result rho }
    ensures { m0.interp_pred = result.interp_pred }
    ensures { forall f':'ls0. f0 <> f' ->
      eval m0.interp_fun f' = eval result.interp_fun f' }
  =
    let rec aux (phi:fo_formula 'ls 'b) (f:'ls)
      (env:fo_term_list 'ls 'b)
      (g:(list 'st) -> ('b -> 'st))
      (m:model 'ls 'st) : model 'ls 'st
      requires { is_skolem_axiom phi f env }
      requires { forall rho:'b -> 'st,x:'b.
        is_fo_term_free_var_in_fo_formula x phi ->
          g (term_list_semantic env m rho) x = rho x }
      requires { not(is_symbol_free_var_in_fo_term_list f env) }
      ensures { forall rho:'b -> 'st.
        formula_semantic phi result rho }
      ensures { m.interp_pred = result.interp_pred }
      ensures { forall f':'ls. f <> f' ->
        eval m.interp_fun f' = eval result.interp_fun f' }
      variant { size_fo_formula phi }
    =
      match phi with
        | Forall phi2 ->
          let renv = rename_fo_term_list env identity some in
          let nenv = FOCons (Var_fo_term None)
            (rename_fo_term_list env identity some) in
          assert { forall rho:(option 'b) -> 'st,x:option 'b.
            is_fo_term_free_var_in_fo_formula x phi2 ->
              let u = term_list_semantic nenv m rho in
              u = Cons (rho None) (term_list_semantic renv m rho) &&
              match x with
              | None -> rho x = extend_env g u x
              | Some y -> is_fo_term_free_var_in_fo_formula y phi &&
                rcompose some rho y = g (term_list_semantic env m
                  (rcompose some rho)) y = g (term_list_semantic renv m rho) y
                  = extend_env g u x
            end } ;
          aux phi2 f nenv (extend_env g) m
        | Or (Not (Exists phi2)) phi3 ->
          let semf = skolem_function phi2 m g in
          let skm = {
            interp_fun = m.interp_fun[f <- semf] ;
            interp_pred = m.interp_pred ;
          } in
          assert { forall rho:'b -> 'st.
            let em = term_list_semantic env m rho in
            let esm = term_list_semantic env skm rho in
            let skt = App (Var_symbol f) env in
            let sks = ocase subst_id_fo_term skt in
            (em = esm) &&
            (phi3 = subst_fo_formula phi2 subst_id_symbol sks) &&
            (forall x:'st.
             let rhox = ocase rho x in
             let rhox2 = ocase (g em) x in
             (forall y:option 'b.
               is_fo_term_free_var_in_fo_formula y phi2 ->
                 rhox y = rhox2 y) &&
             (formula_semantic phi2 m rhox <->
               formula_semantic phi2 m rhox2)) &&
             (let s1 = semantic_subst sks skm rho in
              let s2 = ocase rho (semf esm) in
              s1 None = s2 None &&
              (forall y:'b. s1 (Some y) = s2 (Some y)) &&
              extensionalEqual s1 s2 &&
            (forall f':'ls. is_symbol_free_var_in_fo_formula f' phi2 ->
              eval m.interp_fun f' = eval skm.interp_fun f' /\
              eval m.interp_pred f' = eval skm.interp_pred f') &&
            (forall x:'st.
             let rhox = ocase rho x in
             (formula_semantic phi2 m (ocase (g em) x) ->
              formula_semantic phi2 m (ocase (g em) (semf em)) &&
              formula_semantic phi2 m (ocase rho (semf em)) &&
              formula_semantic phi2 skm s2 &&
              formula_semantic phi3 skm rho) &&
             (formula_semantic phi2 m rhox <-> formula_semantic phi2 skm rhox)
             && (formula_semantic phi2 skm rhox ->
               formula_semantic phi3 skm rho)))
          } ;
          skm
        | _ -> absurd
      end in
    aux phi0 f0 FONil default m0*)

  (*predicate deducible_from (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) (d:demonstration 'ls 'b) =
    match d with
      | Axiom -> formula_list_mem phi gamma
      | ModusPonens d1 d2 phi2 -> deducible_from gamma phi2 d1 /\
        deducible_from gamma (Or (Not phi2) phi) d2
      | Abstraction d phi1 phi2 ->
        deducible_from (FOFCons phi1 gamma) phi2 d /\
        phi = Or (Not phi1) phi2
      | ConjunctionIntro d1 phi1 d2 phi2 ->
        deducible_from gamma phi1 d1 /\
        deducible_from gamma phi2 d2 /\
        phi = And phi1 phi2
      | ConjunctionLeft d phi2 -> deducible_from gamma (And phi phi2) d
      | ConjunctionRight d phi2 -> deducible_from gamma (And phi2 phi) d
      | DisjunctionLeft d phi1 phi2 ->
        deducible_from gamma phi1 d /\ phi = Or phi1 phi2
      | DisjunctionRight d phi1 phi2 ->
        deducible_from gamma phi2 d /\ phi = Or phi1 phi2
      | DisjunctionElim d1 d2 d3 phi1 phi2 ->
        deducible_from gamma (Or phi1 phi2) d1 /\
        deducible_from gamma (Or (Not phi1) phi) d2 /\
        deducible_from gamma (Or (Not phi2) phi) d3
      | UniversalInstantiation d phi2 t ->
        deducible_from gamma (Forall phi2) d /\
        phi = subst_fo_formula phi2 subst_id_symbol (ocase subst_id_fo_term t)
      | Instantiation d gamma2 phi2 x t ->
        let s = subst_id_fo_term[x<-t] in
        deducible_from gamma2 phi2 d /\
        gamma = subst_fo_formula_list gamma2 subst_id_symbol s /\
        phi = subst_fo_formula phi2 subst_id_symbol s
      | ExistentialIntroduction d phi2 t ->
        phi = Exists phi2 /\
        deducible_from gamma (
          subst_fo_formula phi2 subst_id_symbol (ocase subst_id_fo_term t)) d
      | ExistentialElimination d1 d2 phi1 phi2 ->
        deducible_from gamma (Forall (Or (Not phi1) phi2)) d1 /\
        deducible_from gamma (Exists phi1) d2 /\
        phi = Exists phi2
      | PointlessExistential d ->
        deducible_from gamma (Exists
          (rename_fo_formula phi identity some)) d
      | ExFalso d -> deducible_from gamma FFalse d
      | Trivial -> phi = FTrue
      | Weakening d gamma2 ->
        deducible_from gamma2 phi d /\
        (forall phi0:fo_formula 'ls 'b.
          formula_list_mem phi0 gamma2 ->
          formula_list_mem phi0 gamma)
      | Skolemization d phis f ->
        not(is_symbol_free_var_in_fo_formula_list f gamma) /\
        not(is_symbol_free_var_in_fo_formula f phi) /\
        deducible_from (FOFCons phis gamma) phi d /\
        is_skolem_axiom phis f FONil /\
        (forall x:'b. not(is_fo_term_free_var_in_fo_formula x phis))
    end

  let lemma deducible_correct (gamma0:fo_formula_list 'ls 'b0)
    (phi0:fo_formula 'ls 'b0) (d0:demonstration 'ls 'b0)
    (m0:model 'ls 'st0) (rho0:'b0 -> 'st0) : unit
    requires { deducible_from gamma0 phi0 d0 }
    requires { formula_list_conj_semantic gamma0 m0 rho0 }
    ensures { formula_semantic phi0 m0 rho0 }
  =
    let rec ghost aux (gamma:fo_formula_list 'ls 'b)
      (phi:fo_formula 'ls 'b) (d:demonstration 'ls 'b)
      (m:model 'ls 'st) : unit
      requires { deducible_from gamma phi d }
      ensures { forall rho:'b -> 'st.
        formula_list_conj_semantic gamma m rho ->
        formula_semantic phi m rho }
      variant { d }
    =
      match d with
        | Axiom -> ()
        | ModusPonens d1 d2 phi2 ->
          aux gamma phi2 d1 m ; aux gamma (Or (Not phi2) phi) d2 m
        | Abstraction d1 phi1 phi2 ->
          aux (FOFCons phi1 gamma) phi2 d1 m
        | ConjunctionIntro d1 phi1 d2 phi2 ->
          aux gamma phi1 d1 m ; aux gamma phi2 d2 m
        | ConjunctionLeft d1 phi2 -> aux gamma (And phi phi2) d1 m
        | ConjunctionRight d1 phi2 -> aux gamma (And phi2 phi) d1 m
        | DisjunctionLeft d1 phi1 phi2 -> aux gamma phi1 d1 m
        | DisjunctionRight d1 phi1 phi2 -> aux gamma phi2 d1 m
        | DisjunctionElim d1 d2 d3 phi1 phi2 -> aux gamma (Or phi1 phi2) d1 m ;
          aux gamma (Or (Not phi1) phi) d2 m ;
          aux gamma (Or (Not phi2) phi) d3 m
        | UniversalInstantiation d1 phi2 t -> aux gamma (Forall phi2) d1 m ;
          assert { forall rho:'b -> 'st.
            let f1 = semantic_subst (ocase subst_id_fo_term t) m rho in
            let f2 = ocase rho (term_semantic t m rho) in
            (forall x:option 'b. match x with
              | None -> f1 None = f2 None
              | Some z -> f1 (Some z) = f2 (Some z) end && f1 x = f2 x)
            && extensionalEqual f1 f2 && f1 = f2 }
        | Instantiation d gamma2 phi2 x t -> aux gamma2 phi2 d m
        | ExistentialIntroduction d phi2 t ->
          let s = ocase subst_id_fo_term t in
          let phi3 = subst_fo_formula phi2 subst_id_symbol s in
          aux gamma phi3 d m ;
          assert { forall rho:'b -> 'st.
            let f1 = semantic_subst (ocase subst_id_fo_term t) m rho in
            let f2 = ocase rho (term_semantic t m rho) in
            (forall x:option 'b. match x with
              | None -> f1 None = f2 None
              | Some z -> f1 (Some z) = f2 (Some z) end && f1 x = f2 x)
            && extensionalEqual f1 f2 && f1 = f2 }
        | ExistentialElimination d1 d2 phi1 phi2 ->
          aux gamma (Forall (Or (Not phi1) phi2)) d1 m ;
          aux gamma (Exists phi1) d2 m
        | PointlessExistential d ->
          let phi2 = rename_fo_formula phi identity some in
          assert { phi2 = subst_fo_formula phi
            (rcompose identity subst_id_symbol)
            (rcompose some subst_id_fo_term) } ;
          assert { forall rho:'b -> 'st,x:'st.
            formula_semantic phi2 m (ocase rho x) <->
            formula_semantic phi m rho } ;
          aux gamma (Exists (rename_fo_formula phi identity some)) d m
        | ExFalso d -> aux gamma FFalse d m
        | Trivial -> ()
        | Weakening d2 gamma2 -> aux gamma2 phi d2 m
        | Skolemization d phis f ->
          let skm = skolemized_model phis f m in
          assert { forall rho:'b -> 'st.
            (formula_list_conj_semantic gamma m rho
              <-> formula_list_conj_semantic gamma skm rho) /\
            (formula_semantic phi m rho <->
              formula_semantic phi skm rho) } ;
          aux (FOFCons phis gamma) phi d skm
        | _ -> absurd
      end in
    aux gamma0 phi0 d0 m0

  predicate entail (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) =
    exists d:demonstration 'ls 'b.
      deducible_from gamma phi d

  predicate unsat (gamma:fo_formula_list 'ls 'b) =
    entail gamma FFalse

  predicate valid (phi:fo_formula 'ls 'b) =
    entail FOFNil phi

  lemma entail_correct : forall gamma:fo_formula_list 'ls 'b,
    phi:fo_formula 'ls 'b,m:model 'ls 'st,rho:'b -> 'st.
    entail gamma phi ->
      formula_list_conj_semantic gamma m rho -> formula_semantic phi m rho

  lemma unsat_correct : forall gamma:fo_formula_list 'ls 'b,
    m:model 'ls 'st,rho:'b -> 'st.
    unsat gamma -> not(formula_list_conj_semantic gamma m rho)

  lemma valid_correct : forall phi:fo_formula 'ls 'b,
    m:model 'ls 'st,rho:'b -> 'st.
    valid phi -> formula_semantic phi m rho

  function imply (phi1 phi2:fo_formula 'ls 'b) : fo_formula 'ls 'b =
    Or (Not phi1) phi2

  function equiv (phi1 phi2:fo_formula 'ls 'b) : fo_formula 'ls 'b =
    And (imply phi1 phi2) (imply phi2 phi1)

  type sequent 'ls 'b = {
    demo : demonstration 'ls 'b ;
    context : fo_formula_list 'ls 'b ;
    conclusion : fo_formula 'ls 'b ;
  }

  predicate sequent_correct (s:sequent 'ls 'b) =
    deducible_from s.context s.conclusion s.demo

  let ghost make_axiom (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) : sequent 'ls 'b
    requires { formula_list_mem phi gamma }
    ensures { sequent_correct result }
    ensures { result.context = gamma }
    ensures { result.conclusion = phi }
  = { demo = Axiom ; context = gamma ; conclusion = phi }

  let ghost make_abstraction (s:sequent 'ls 'b) :
    sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.context with FOFNil -> false | _ -> true end }
    ensures { sequent_correct result }
    ensures { match s.context with FOFNil -> false | FOFCons x q ->
      result.context = q /\ result.conclusion = imply x s.conclusion end }
  = match s.context with FOFNil -> absurd
      | FOFCons x q -> { demo = Abstraction s.demo x s.conclusion ;
        context = q ; conclusion = imply x s.conclusion }
    end

  let ghost modus_ponens (s1 s2:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s1 /\ sequent_correct s2 }
    requires { match s1.conclusion with
      | Or (Not phi1) phi2 -> s2.conclusion = phi1
      | _ -> false end }
    requires { s1.context = s2.context }
    ensures { sequent_correct result }
    ensures { result.context = s1.context }
    ensures { match s1.conclusion with
      | Or _ phi2 -> result.conclusion = phi2
      | _ -> false end }
  = match s1.conclusion with
    | Or (Not phi1) phi2 -> { demo = ModusPonens s2.demo s1.demo phi1 ;
        context = s1.context ;
        conclusion = phi2 }
    | _ -> absurd end

  let ghost make_classical (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) : sequent 'ls 'b
    ensures { sequent_correct result }
    ensures { result.context = gamma }
    ensures { result.conclusion = Or (Not phi) phi }
  = make_abstraction (make_axiom (FOFCons phi gamma) phi)

  let ghost disjunction_elimination (s1 s2 s3:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s1 /\ sequent_correct s2 /\ sequent_correct s3 }
    requires { match s1.conclusion , s2.conclusion , s3.conclusion with
      | Or phi1 phi2 , Or (Not phi1') phi3 , Or (Not phi2') phi3' ->
        phi1 = phi1' /\ phi2 = phi2' /\ phi3 = phi3'
      | _ -> false end }
    requires { s1.context = s2.context = s3.context }
    ensures { sequent_correct result }
    ensures { result.context = s1.context }
    ensures { match s3.conclusion with
      | Or _ phi3 -> result.conclusion = phi3
      | _ -> false end }
  =
    match s1.conclusion , s3.conclusion with
      | Or phi1 phi2 , Or _ phi3 ->
        { demo = DisjunctionElim s1.demo s2.demo s3.demo phi1 phi2 ;
          context = s1.context ;
          conclusion = phi3 ; }
      | _ -> absurd
    end

  let ghost disjunction_left (s:sequent 'ls 'b) (phi2:fo_formula 'ls 'b) :
    sequent 'ls 'b
    requires { sequent_correct s }
    ensures { sequent_correct result }
    ensures { result.conclusion = Or s.conclusion phi2 }
    ensures { s.context = result.context }
  =
    { demo = DisjunctionLeft s.demo s.conclusion phi2 ;
      context = s.context ;
      conclusion = Or s.conclusion phi2 }

  let ghost disjunction_right (phi1:fo_formula 'ls 'b) (s:sequent 'ls 'b) :
    sequent 'ls 'b
    requires { sequent_correct s }
    ensures { sequent_correct result }
    ensures { result.conclusion = Or phi1 s.conclusion }
    ensures { s.context = result.context }
  =
    { demo = DisjunctionRight s.demo phi1 s.conclusion ;
      context = s.context ;
      conclusion = Or phi1 s.conclusion }

  let ghost conjunction (s1 s2:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s1 /\ sequent_correct s2 }
    requires { s1.context = s2.context }
    ensures { sequent_correct result }
    ensures { result.context = s1.context }
    ensures { result.conclusion = And s1.conclusion s2.conclusion }
  =
    { demo = ConjunctionIntro s1.demo s1.conclusion s2.demo s2.conclusion ;
      context = s1.context ;
      conclusion = And s1.conclusion s2.conclusion }

  let ghost conjunction_left (s:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.conclusion with And _ _ -> true | _ -> false end }
    ensures { sequent_correct result }
    ensures { result.context = s.context }
    ensures { match s.conclusion with And phi _ -> result.conclusion = phi
      | _ -> false end }
  = match s.conclusion with
      | And phi1 phi2 -> { demo = ConjunctionLeft s.demo phi2 ;
        context = s.context ;
        conclusion = phi1 }
      | _ -> absurd
    end

  let ghost conjunction_right (s:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.conclusion with And _ _ -> true | _ -> false end }
    ensures { sequent_correct result }
    ensures { result.context = s.context }
    ensures { match s.conclusion with And _ phi -> result.conclusion = phi
      | _ -> false end }
  = match s.conclusion with
      | And phi2 phi1 -> { demo = ConjunctionRight s.demo phi2 ;
        context = s.context ;
        conclusion = phi1 }
      | _ -> absurd
    end

  let ghost exfalso (s:sequent 'ls 'b) (phi:fo_formula 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.conclusion with FFalse -> true | _ -> false end }
    ensures { sequent_correct result }
    ensures { result.context = s.context }
    ensures { result.conclusion = phi }
  = { demo = ExFalso s.demo ; context = s.context ; conclusion = phi }

  let ghost make_trivial (gamma:fo_formula_list 'ls 'b) : sequent 'ls 'b
    ensures { sequent_correct result }
    ensures { result.context = gamma }
    ensures { result.conclusion = FTrue }
  = { demo = Trivial ; context = gamma ; conclusion = FTrue }

  let ghost weaken (gamma:fo_formula_list 'ls 'b)
    (s:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { forall phi:fo_formula 'ls 'b.
      formula_list_mem phi s.context -> formula_list_mem phi gamma }
    ensures { sequent_correct result }
    ensures { result.context = gamma }
    ensures { result.conclusion = s.conclusion }
  = { demo = Weakening s.demo s.context ;
      context = gamma ;
      conclusion = s.conclusion }

  let ghost skolem_elim (f:'ls) (s:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.context with FOFCons phis gamma ->
      is_skolem_axiom phis f FONil /\
      (forall x:'b. not(is_fo_term_free_var_in_fo_formula x phis)) /\
      not(is_symbol_free_var_in_fo_formula_list f gamma)
      | _ -> false end }
    requires { not(is_symbol_free_var_in_fo_formula f s.conclusion) }
    ensures { sequent_correct result }
    ensures { match s.context with FOFCons _ gamma -> result.context = gamma
      | _ -> false end }
    ensures { result.conclusion = s.conclusion }
  = match s.context with FOFCons phis gamma ->
      { demo = Skolemization s.demo phis f ;
        context = gamma ;
        conclusion = s.conclusion }
      | _ -> absurd end

  let ghost conjunction_commutative (s:sequent 'ls 'b) : sequent 'ls 'b
    requires { sequent_correct s }
    requires { match s.conclusion with And _ _ -> true | _ -> false end }
    ensures { sequent_correct result }
    ensures { result.context = s.context }
    ensures { match s.conclusion with And phi1 phi2 ->
      result.conclusion = And phi2 phi1 | _ -> false end }
  = conjunction (conjunction_right s) (conjunction_left s)

  let ghost equiv_reflexive (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) : sequent 'ls 'b
    ensures { sequent_correct result }
    ensures { result.context = gamma }
    ensures { result.conclusion = equiv phi phi }
  =
    let u = make_classical gamma phi in
    conjunction u u

  (*

  let ghost imply_or_morphism (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2 phi3 phi4:fo_formula 'ls 'b)
    (d1 d2:demonstration 'ls 'b) : demonstration 'ls 'b
    requires { deducible_from gamma (imply phi1 phi3) d1 }
    requires { deducible_from gamma (imply phi2 phi4) d2 }
    ensures { deducible_from gamma
     (imply (Or phi1 phi2) (Or phi3 phi4)) result }
  =
    let o34 = Or phi3 phi4 in
    let o12 = Or phi1 phi2 in
    let gamma12 = FOFCons o12 gamma in
    let gamma121 = FOFCons phi1 gamma12 in
    let gamma122 = FOFCons phi2 gamma12 in
    let d1 = modus_ponens gamma121 phi1 phi3
      (weaken gamma gamma121 (imply phi1 phi3) d1)
      (make_axiom gamma121 phi1) in
    let d2 = modus_ponens gamma122 phi2 phi4
      (weaken gamma gamma122 (imply phi2 phi4) d2)
      (make_axiom gamma122 phi2) in
    disjunction_elimination gamma12 phi1 phi2 o34
      (make_axiom gamma12 o12)
      (make_abstraction gamma12 phi1 o34
        (disjunction_left gamma121 phi3 phi4 d1))
      (make_abstraction gamma12 phi2 o34
        (disjunction_right gamma122 phi3 phi4 d2))

  let ghost equiv_or_morphism (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2 phi3 phi4:fo_formula 'ls 'b)
    (d1 d2:demonstration 'ls 'b) : demonstration 'ls 'b
    requires { deducible_from gamma (equiv phi1 phi3) d1 }
    requires { deducible_from gamma (equiv phi2 phi4) d2 }
    ensures { deducible_from gamma
      (equiv (Or phi1 phi2) (Or phi3 phi4)) result }
  =
    let (o12,o34) = (Or phi1 phi2,Or phi3 phi4) in
    let way1 = imply_or_morphism gamma phi1 phi2 phi3 phi4
      (conjunction_left gamma (imply phi1 phi3) (imply phi3 phi1) d1)
      (conjunction_left gamma (imply phi2 phi4) (imply phi4 phi2) d2) in
    let way2 = imply_or_morphism gamma phi3 phi4 phi1 phi2
      (conjunction_right gamma (imply phi1 phi3) (imply phi3 phi1) d1)
      (conjunction_right gamma (imply phi2 phi4) (imply phi4 phi2) d2) in
    conjunction (imply o12 o34) (imply o34 o12) way1 way2
  *)


  (*
  let ghost disjunction_commutation (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Or phi1 phi2) d }
    ensures { deducible_from gamma (Or phi2 phi1) result }
  =
    let o = Or phi2 phi1 in
    let (gamma1,gamma2) = (FOFCons phi1 gamma,FOFCons phi2 gamma) in
    let d1 = disjunction_right gamma1 phi2 phi1 (make_axiom gamma1 phi1) in
    let d2 = disjunction_left gamma2 phi2 phi1 (make_axiom gamma2 phi2) in
    let d1 = make_abstraction gamma phi1 o d1 in
    let d2 = make_abstraction gamma phi2 o d2 in
    disjunction_elimination gamma phi1 phi2 o d d1 d2

  let ghost disjunction_associative_r (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2 phi3:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Or (Or phi1 phi2) phi3) d }
    ensures { deducible_from gamma (Or phi1 (Or phi2 phi3)) result }
  =
    let o12 = Or phi1 phi2 in
    let o23 = Or phi2 phi3 in
    let ob = Or o12 phi3 in
    let oa = Or phi1 o23 in
    let gamma12 = FOFCons o12 gamma in
    let gamma1 = FOFCons phi1 gamma12 in
    let gamma2 = FOFCons phi2 gamma12 in
    let gamma3 = FOFCons phi3 gamma in
    let d1 = disjunction_left gamma1 phi1 o23
      (make_axiom gamma1 phi1) in
    let d2 = disjunction_right gamma2 phi1 o23
      (disjunction_left gamma2 phi2 phi3 (make_axiom gamma2 phi2)) in
    let d1 = make_abstraction gamma12 phi1 oa d1 in
    let d2 = make_abstraction gamma12 phi2 oa d2 in
    let d12 = disjunction_elimination gamma12 phi1 phi2 oa
      (make_axiom gamma12 o12) d1 d2 in
    let d3 = disjunction_right gamma3 phi1 o23
      (disjunction_right gamma3 phi2 phi3 (make_axiom gamma3 phi3)) in
    let d12 = make_abstraction gamma o12 oa d12 in
    let d3 = make_abstraction gamma phi3 oa d3 in
    disjunction_elimination gamma o12 phi3 oa d d12 d3

  let ghost disjunction_associative_l (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2 phi3:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Or phi1 (Or phi2 phi3)) d }
    ensures { deducible_from gamma (Or (Or phi1 phi2) phi3) result }
  =
    let o23 = Or phi2 phi3 in
    let o12 = Or phi1 phi2 in
    let ob = Or phi1 o23 in
    let oa = Or o12 phi3 in
    let gamma23 = FOFCons o23 gamma in
    let gamma3 = FOFCons phi3 gamma23 in
    let gamma2 = FOFCons phi2 gamma23 in
    let gamma1 = FOFCons phi1 gamma in
    let d3 = disjunction_right gamma3 o12 phi3
      (make_axiom gamma3 phi3) in
    let d2 = disjunction_left gamma2 o12 phi3
      (disjunction_right gamma2 phi1 phi2 (make_axiom gamma2 phi2)) in
    let d3 = make_abstraction gamma23 phi3 oa d3 in
    let d2 = make_abstraction gamma23 phi2 oa d2 in
    let d23 = disjunction_elimination gamma23 phi2 phi3 oa
      (make_axiom gamma23 o23) d2 d3 in
    let d1 = disjunction_left gamma1 o12 phi3
      (disjunction_left gamma1 phi1 phi2 (make_axiom gamma1 phi1)) in
    let d23 = make_abstraction gamma o23 oa d23 in
    let d1 = make_abstraction gamma phi1 oa d1 in
    disjunction_elimination gamma phi1 o23 oa d d1 d23
  *)

  (*
  let ghost double_negation_elimination (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Not (Not phi)) d }
    ensures { deducible_from gamma phi result }
  =
    let nphi = Not phi in
    let nnphi = Not nphi in
    let d0 = make_classical gamma phi in
    let d2 = disjunction_left gamma nnphi phi d in
    disjunction_elimination gamma nphi phi phi d0 d2 d0
  *)

  (*let ghost false_neutral_left_disjunction (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Or FFalse phi) d }
    ensures { deducible_from gamma phi result }
  =
    let d1 = Abstraction (ExFalso Axiom) FFalse phi in
    let d2 = Abstraction Axiom phi phi in
    DisjunctionElim d d1 d2 FFalse phi

  let ghost false_neutral_right_disjunction (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) (d:demonstration 'ls 'b) :
    demonstration 'ls 'b
    requires { deducible_from gamma (Or phi FFalse) d }
    ensures { deducible_from gamma phi result }
  =
    false_neutral_left_disjunction gamma phi
      (disjunction_commutation gamma phi FFalse d)*)

  (*(* Now we do not need the demonstration object anymore. *)

  let lemma entail_axiom (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) : unit
    requires { formula_list_mem phi gamma }
    ensures { entail gamma phi }
  =
    ()

  let lemma entail_modus_ponens (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b) : unit
    requires { entail gamma phi1 /\ entail gamma (Or (Not phi1) phi2) }
    ensures { entail gamma phi2 }
  =
    assert { forall d1 d2:demonstration 'ls 'b.
      deducible_from gamma phi1 d1 /\
      deducible_from gamma (Or (Not phi1) phi2) d2 ->
      deducible_from gamma phi2 (ModusPonens d1 d2 phi1) }

  let lemma entail_abstraction (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b) : unit
    requires { entail (FOFCons phi1 gamma) phi2 }
    ensures { entail gamma (Or (Not phi1) phi2) }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from (FOFCons phi1 gamma) phi2 d ->
      deducible_from gamma (Or (Not phi1) phi2) (Abstraction d phi1 phi2) }

  let lemma entail_conjunction (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b) : unit
    ensures { entail gamma (And phi1 phi2)
      <-> entail gamma phi1 /\ entail gamma phi2 }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma (And phi1 phi2) d ->
      deducible_from gamma phi1 (ConjunctionLeft d phi2) /\
      deducible_from gamma phi2 (ConjunctionRight d phi1) } ;
    assert { forall d1 d2:demonstration 'ls 'b.
      deducible_from gamma phi1 d1 /\
      deducible_from gamma phi2 d2 ->
      deducible_from gamma (And phi1 phi2) (ConjunctionIntro d1 phi1 d2 phi2) }

  let lemma entail_disjunction_left (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b)
    requires { entail gamma phi1 }
    ensures { entail gamma (Or phi1 phi2) }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma phi1 d ->
      deducible_from gamma (Or phi1 phi2) (DisjunctionLeft d phi1 phi2) }

  let lemma entail_disjunction_right (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b)
    requires { entail gamma phi2 }
    ensures { entail gamma (Or phi1 phi2) }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma phi2 d ->
      deducible_from gamma (Or phi1 phi2) (DisjunctionRight d phi1 phi2) }

  let lemma entail_disjunction_elim (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2 phi3:fo_formula 'ls 'b)
    requires { entail gamma (Or phi1 phi2) }
    requires { entail gamma (Or (Not phi1) phi3) }
    requires { entail gamma (Or (Not phi2) phi3) }
    ensures { entail gamma phi3 }
  =
    assert { forall d1 d2 d3:demonstration 'ls 'b.
      deducible_from gamma (Or phi1 phi2) d1 /\
      deducible_from gamma (Or (Not phi1) phi3) d2 /\
      deducible_from gamma (Or (Not phi2) phi3) d3 ->
      deducible_from gamma phi3 (DisjunctionElim d1 d2 d3 phi1 phi2) }

  let lemma entail_universal_instantiation (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls (option 'b)) (t:fo_term 'ls 'b)
    requires { entail gamma (Forall phi) }
    ensures { entail gamma (subst_fo_formula phi
      (ocase subst_id_fo_term t)) }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma (Forall phi) d ->
      deducible_from gamma (subst_fo_formula phi
        (ocase subst_id_fo_term t)) (UniversalInstantiation d phi t) }

  let lemma entail_instantiation (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls 'b) (x:'b) (t:fo_term 'ls 'b)
    requires { entail gamma phi }
    ensures { let s = subst_id_fo_term[x<-t] in
      entail (subst_fo_formula_list gamma s) (subst_fo_formula phi s) }
  =
    let s = subst_id_fo_term[x<-t] in
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma phi d ->
      deducible_from (subst_fo_formula_list gamma s)
        (subst_fo_formula phi s) (Instantiation d gamma phi x t) }

  let lemma entail_existential_introduction (gamma:fo_formula_list 'ls 'b)
    (phi:fo_formula 'ls (option 'b)) (t:fo_term 'ls 'b)
    requires { entail gamma (subst_fo_formula phi
      (ocase subst_id_fo_term t)) }
    ensures { entail gamma (Exists phi) }
  =
    assert { forall d:demonstration 'ls 'b.
      deducible_from gamma (subst_fo_formula phi
        (ocase subst_id_fo_term t)) d ->
      deducible_from gamma (Exists phi) (ExistentialIntroduction d phi t) }

  let lemma entail_existential_elimination (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls (option 'b))
    requires { entail gamma (Forall (Or (Not phi1) phi2)) }
    requires { entail gamma (Exists phi1) }
    ensures { entail gamma (Exists phi2) }
  =
    assert { forall d1 d2:demonstration 'ls 'b.
      deducible_from gamma (Forall (Or (Not phi1) phi2)) d1 /\
      deducible_from gamma (Exists phi1) d2 ->
      deducible_from gamma (Exists phi2)
        (ExistentialElimination d1 d2 phi1 phi2) }

  let lemma disjunction_commutative (gamma:fo_formula_list 'ls 'b)
    (phi1 phi2:fo_formula 'ls 'b)
    requires { entail gamma (Or phi1 phi2) }
    ensures { entail gamma (Or phi2 phi1) }
  =
    entail_axiom (FOFCons phi1 gamma) phi1 ;
    entail_axiom (FOFCons phi2 gamma) phi2 ;
    entail_disjunction_right (FOFCons phi1 gamma) phi2 phi1 ;
    entail_disjunction_left (FOFCons phi2 gamma) phi2 phi1 ;
    entail_disjunction_elim gamma phi1 phi2 (Or phi2 phi1)*)
  *)
end
