Theory Variable_Substitution

theory Variable_Substitution
  imports
    Abstract_Substitution.Substitution
    "HOL-Library.FSet"
    "HOL-Library.Multiset"
begin

locale finite_set =
  fixes set :: "'b  'a set"
  assumes finite_set [simp]: "b. finite (set b)"
begin

abbreviation finite_set :: "'b  'a fset" where 
  "finite_set b  Abs_fset (set b)"

lemma finite_set': "set b  {A. finite A}"
  by simp

lemma fset_finite_set [simp]: "fset (finite_set b) = set b"
  using Abs_fset_inverse[OF finite_set'].

end

locale variable_substitution = substitution _ _ subst "λa. vars a = {}" 
for
  subst :: "'expression  ('variable  'base_expression)  'expression" (infixl "" 70) and
  vars :: "'expression  'variable set" +
assumes 
  subst_eq: "a σ τ. (x. x  (vars a)  σ x = τ x)  a  σ = a  τ"
begin

abbreviation is_ground where "is_ground a  vars a = {}"

definition vars_set :: "'expression set  'variable set" where
  "vars_set expressions  expression  expressions. vars expression"

lemma subst_reduntant_upd [simp]:
  assumes "var  vars a"
  shows "a  σ(var := update) = a  σ"
  using assms subst_eq
  by fastforce

lemma subst_reduntant_if [simp]: 
  assumes "vars a  vars'"
  shows "a  (λvar. if var  vars' then σ var else σ' var) = a  σ"
  using assms 
  by (smt (verit, best) subset_eq subst_eq)

lemma subst_reduntant_if' [simp]: 
  assumes "vars a  vars' = {}"  
  shows "a  (λvar. if var  vars' then σ' var else σ var) = a  σ"
  using assms subst_eq 
  unfolding disjoint_iff
  by presburger

lemma subst_cannot_unground:
  assumes "¬is_ground (a  σ)"  
  shows "¬is_ground a" 
  using assms by force

end

locale finite_variables = finite_set vars for vars :: "'expression  'variable set"
begin

lemmas finite_vars = finite_set finite_set'
lemmas fset_finite_vars = fset_finite_set

abbreviation "finite_vars  finite_set"

end

locale all_subst_ident_iff_ground =
  fixes is_ground :: "'expression  bool" and subst 
  assumes
    all_subst_ident_iff_ground: "a. is_ground a  (σ. subst a σ = a)" and
    exists_non_ident_subst: 
      "a s. finite s  ¬is_ground a  σ. subst a σ  a  subst a σ  s"

locale grounding = variable_substitution 
  where vars = vars for vars :: "'a  'var set" +
  fixes to_ground :: "'a  'g" and from_ground :: "'g  'a"
  assumes 
    range_from_ground_iff_is_ground: "{f. is_ground f} = range from_ground" and
    from_ground_inverse [simp]: "g. to_ground (from_ground g) = g"
begin

definition groundings ::"'a  'g set" where
  "groundings a = { to_ground (a  γ) | γ. is_ground (a  γ) }"

lemma to_ground_from_ground_id: "to_ground  from_ground = id"
  using from_ground_inverse
  by auto

lemma surj_to_ground: "surj to_ground"
  using from_ground_inverse
  by (metis surj_def)

lemma inj_from_ground: "inj_on from_ground domainG"
  by (metis from_ground_inverse inj_on_inverseI)
 
lemma inj_on_to_ground: "inj_on to_ground (from_ground ` domainG)"
  unfolding inj_on_def
  by simp

lemma bij_betw_to_ground: "bij_betw to_ground (from_ground ` domainG) domainG"
  by (smt (verit, best) bij_betwI' from_ground_inverse image_iff)

lemma bij_betw_from_ground: "bij_betw from_ground domainG (from_ground ` domainG)"
  by (simp add: bij_betw_def inj_from_ground)

lemma ground_is_ground [simp, intro]: "is_ground (from_ground g)"
  using range_from_ground_iff_is_ground
  by blast

lemma is_ground_iff_range_from_ground: "is_ground f  f  range from_ground"
  using range_from_ground_iff_is_ground
  by auto

lemma to_ground_inverse [simp]: 
  assumes "is_ground f"
  shows "from_ground (to_ground f) = f"
  using inj_on_to_ground from_ground_inverse is_ground_iff_range_from_ground assms
  unfolding inj_on_def
  by blast

corollary obtain_grounding: 
  assumes "is_ground f"
  obtains g where "from_ground g = f"
  using to_ground_inverse assms by blast

end

locale base_variable_substitution = variable_substitution 
  where subst = subst
  for subst :: "'expression  ('variable  'expression)  'expression"  (infixl "" 70) +
  assumes
    is_grounding_iff_vars_grounded: 
      "exp. is_ground (exp  γ)  (x  vars exp. is_ground (γ x))" and
    ground_exists: "exp. is_ground exp"
begin 

lemma obtain_ground_subst:
  obtains γ 
  where "is_ground_subst γ"
proof-
  obtain g where "is_ground g"
    using ground_exists by blast

  then have "is_ground_subst  (λ_. g)"
    by (simp add: is_grounding_iff_vars_grounded is_ground_subst_def)

  then show ?thesis
    using that
    by simp
qed

lemma ground_subst_extension:
  assumes "is_ground (exp  γ)"
  obtains γ'
  where "exp  γ = exp  γ'" and "is_ground_subst γ'"
proof-
  obtain γ'' where 
    γ'': "is_ground_subst γ''"
    using obtain_ground_subst
    by blast
                    
  define γ' where 
    γ':  "γ' = (λvar. if var  vars exp then γ var else γ'' var)"

  have "is_ground_subst γ'"
    using assms γ'' is_grounding_iff_vars_grounded
    unfolding γ' is_ground_subst_def
    by simp

  moreover have "exp  γ = exp  γ'"
    unfolding γ'
    using subst_eq by presburger

  ultimately show ?thesis
    using that
    by blast
qed

lemma ground_subst_upd [simp]:
  assumes "is_ground update" "is_ground (exp  γ)" 
  shows "is_ground (exp  γ(var := update))"
  using assms is_grounding_iff_vars_grounded by auto

lemma variable_grounding:
  assumes "is_ground (t  γ)" "x  vars t" 
  shows "is_ground (γ x)"
  using assms is_grounding_iff_vars_grounded 
  by blast

end

locale based_variable_substitution = 
  base: base_variable_substitution where subst = base_subst and vars = base_vars + 
  variable_substitution
for base_subst base_vars +
assumes
  ground_subst_iff_base_ground_subst [simp]: "is_ground_subst γ  base.is_ground_subst γ" and
  is_grounding_iff_vars_grounded: 
    "exp. is_ground (exp  γ)  (x  vars exp. base.is_ground (γ x))"
begin

lemma obtain_ground_subst:
  obtains γ 
  where "is_ground_subst γ"
  using base.obtain_ground_subst by auto

lemma ground_subst_extension:
  assumes "is_ground (exp  γ)"
  obtains γ'
  where "exp  γ = exp  γ'" and "is_ground_subst γ'"
  using obtain_ground_subst assms
  by (metis all_subst_ident_if_ground is_ground_subst_comp_right subst_comp_subst)

lemma ground_subst_extension':
  assumes "is_ground (exp  γ)"
  obtains γ'
  where "exp  γ = exp  γ'" and "base.is_ground_subst γ'"
  using ground_subst_extension assms
  by auto

lemma ground_subst_upd [simp]:
  assumes "base.is_ground update" "is_ground (exp  γ)" 
  shows "is_ground (exp  γ(var := update))"
  using base.ground_subst_upd assms is_grounding_iff_vars_grounded by simp  

lemma ground_exists: "exp. is_ground exp"
  using base.ground_exists
  by (meson is_grounding_iff_vars_grounded)

lemma variable_grounding:
  assumes "is_ground (t  γ)" "x  vars t" 
  shows "base.is_ground (γ x)"
  using assms is_grounding_iff_vars_grounded 
  by blast

end
                                             
section ‹Liftings›

locale variable_substitution_lifting = 
  sub: variable_substitution
  where subst = sub_subst and vars = sub_vars
  for
    sub_vars :: "'sub_expression  'variable set" and
    sub_subst :: "'sub_expression  ('variable  'base_expression)  'sub_expression" +
  fixes
    map :: "('sub_expression  'sub_expression)  'expression  'expression" and
    to_set :: "'expression  'sub_expression set"
  assumes
    map_comp: "d f g. map f (map g d) = map (f  g) d" and
    map_id: "map id d = d" and
    map_cong: "d f g. (c. c  to_set d  f c = g c)  map f d = map g d" and
    to_set_map: "d f. to_set (map f d) = f ` to_set d" and
    exists_expression: "c. d. c  to_set d" 
begin

definition vars :: "'expression  'variable set" where
  "vars d   (sub_vars ` to_set d)"

definition subst :: "'expression  ('variable  'base_expression)  'expression" where
  "subst d σ  map (λc. sub_subst c σ) d"

lemma map_id_cong: 
  assumes "c. c  to_set d  f c = c"  
  shows "map f d = d"
  using map_cong map_id assms
  unfolding id_def
  by metis

lemma to_set_map_not_ident: 
  assumes "c  to_set d" "f c  to_set d" 
  shows "map f d  d"
  using assms
  by (metis rev_image_eqI to_set_map)

lemma subst_in_to_set_subst:
  assumes "c  to_set d" 
  shows "sub_subst c σ  to_set (subst d σ)"
  unfolding subst_def
  using assms to_set_map by auto

sublocale variable_substitution where subst = subst and vars = vars
proof unfold_locales
  show "x a b. subst x (comp_subst a b) = subst (subst x a) b"
    using sub.subst_comp_subst
    unfolding subst_def map_comp comp_apply
    by presburger
next
  show "x. subst x id_subst = x"
    using map_id
    unfolding subst_def sub.subst_id_subst id_def.
next
   show "x. vars x = {}  σ. subst x σ = x"
     unfolding vars_def subst_def    
     using map_id_cong 
     by simp
next
  show "a σ τ. (x. x  vars a  σ x = τ x)  subst a σ = subst a τ "
    unfolding vars_def subst_def
    using map_cong sub.subst_eq
    by (meson UN_I)
qed

lemma ground_subst_iff_sub_ground_subst [simp]: 
  "is_ground_subst γ  sub.is_ground_subst γ"
proof(unfold is_ground_subst_def sub.is_ground_subst_def, intro iffI allI)
  fix c 
  assume all_d_ground: "d. is_ground (subst d γ)"
  show "sub.is_ground (sub_subst c γ)"
  proof(rule ccontr)
    assume c_not_ground: "¬sub.is_ground (sub_subst c γ)"

    then obtain d where "c  to_set d"
      using exists_expression by auto

    then have "¬is_ground (subst d γ)"
      using c_not_ground to_set_map 
      unfolding subst_def vars_def
      by auto

    then show False
      using all_d_ground
      by blast
  qed
next
  fix d
  assume all_c_ground: "c. sub.is_ground (sub_subst c γ)"

  then show "is_ground (subst d γ)"
    unfolding vars_def subst_def
    using to_set_map
    by simp
qed

lemma to_set_is_ground [intro]:
  assumes "sub  to_set expr" "is_ground expr"
  shows "sub.is_ground sub"
  using assms
  by (simp add: vars_def)

lemma to_set_is_ground_subst:
  assumes "sub  to_set expr"  "is_ground (subst expr γ)"  
  shows "sub.is_ground (sub_subst sub γ)"
  using assms
  by (meson subst_in_to_set_subst to_set_is_ground)

lemma subst_empty:
  assumes "to_set expr' = {}"
  shows "subst expr σ = expr'  expr = expr'"
  using assms map_id_cong subst_def to_set_map 
  by fastforce

lemma empty_is_ground:
  assumes "to_set expr = {}"  
  shows "is_ground expr"
  using assms
  by (simp add: vars_def)

end

locale based_variable_substitution_lifting = 
  variable_substitution_lifting +
  base: base_variable_substitution where subst = base_subst and vars = base_vars
for base_subst base_vars +
assumes 
  sub_is_grounding_iff_vars_grounded: 
    "exp γ. sub.is_ground (sub_subst exp γ)  (x  sub_vars exp. base.is_ground (γ x))" and
  sub_ground_subst_iff_base_ground_subst: "γ. sub.is_ground_subst γ  base.is_ground_subst γ"
begin

lemma is_grounding_iff_vars_grounded: 
  "is_ground (subst exp γ)  (x  vars exp. base.is_ground (γ x))"
  using sub_is_grounding_iff_vars_grounded subst_def to_set_map vars_def
  by auto

lemma ground_subst_iff_base_ground_subst [simp]: 
  "γ. is_ground_subst γ  base.is_ground_subst γ"
  using sub_ground_subst_iff_base_ground_subst ground_subst_iff_sub_ground_subst by blast

lemma obtain_ground_subst:
  obtains γ 
  where "is_ground_subst γ"
  using base.obtain_ground_subst
  by (meson base.ground_exists is_grounding_iff_vars_grounded is_ground_subst_def that)

lemma ground_subst_extension:
  assumes "is_ground (subst exp γ)"
  obtains γ'
  where "subst exp γ = subst exp γ'" and "is_ground_subst γ'"
  by (metis all_subst_ident_if_ground assms comp_subst.left.monoid_action_compatibility 
        is_ground_subst_comp_right obtain_ground_subst)

lemma ground_subst_extension':
  assumes "is_ground (subst exp γ)"
  obtains γ'
  where "subst exp γ = subst exp γ'" and "base.is_ground_subst γ'"
  by (metis all_subst_ident_if_ground assms base.is_ground_subst_comp_right 
        base.obtain_ground_subst subst_comp_subst)
 
lemma ground_subst_upd [simp]:
  assumes "base.is_ground update" "is_ground (subst exp γ)" 
  shows "is_ground (subst exp  (γ(var := update)))"
  using assms(1) assms(2) is_grounding_iff_vars_grounded by auto

lemma ground_exists: "exp. is_ground exp"
  using base.ground_exists
  by (meson is_grounding_iff_vars_grounded)

lemma variable_grounding:
  assumes "is_ground (subst t γ)" "x  vars t" 
  shows "base.is_ground (γ x)"
  using assms is_grounding_iff_vars_grounded 
  by blast

end

locale finite_variables_lifting = 
  variable_substitution_lifting + 
  sub: finite_variables where vars = sub_vars +
  to_set: finite_set where set = to_set
begin

abbreviation to_fset :: "'d  'c fset" where 
  "to_fset  to_set.finite_set"

lemmas finite_to_set = to_set.finite_set to_set.finite_set'
lemmas fset_to_fset = to_set.fset_finite_set

sublocale finite_variables where vars = vars
  by unfold_locales (simp add: vars_def)

end

locale grounding_lifting = 
  variable_substitution_lifting where sub_vars = sub_vars and sub_subst = sub_subst and map = map + 
  sub: grounding where vars = sub_vars and subst = sub_subst and to_ground = sub_to_ground and 
  from_ground = sub_from_ground 
for
  sub_to_ground :: "'sub  'ground_sub" and 
  sub_from_ground :: "'ground_sub  'sub" and
  sub_vars :: "'sub  'variable set" and 
  sub_subst :: "'sub  ('variable  'base)  'sub" and
  map :: "('sub  'sub)  'expr  'expr" +
fixes
  to_ground_map :: "('sub  'ground_sub)  'expr  'ground_expr" and
  from_ground_map :: "('ground_sub  'sub)  'ground_expr  'expr" and
  ground_map :: "('ground_sub  'ground_sub)  'ground_expr  'ground_expr" and
  to_set_ground :: "'ground_expr  'ground_sub set"
assumes
  to_set_from_ground_map: "d f. to_set (from_ground_map f d) = f ` to_set_ground d" and
  map_comp': "d f g. from_ground_map f (to_ground_map g d) = map (f  g) d" and
  ground_map_comp: "d f g. to_ground_map f (from_ground_map g d) = ground_map (f  g) d" and
  ground_map_id: "ground_map id g = g"
begin

definition to_ground where "to_ground expr  to_ground_map sub_to_ground expr"

definition from_ground where "from_ground expr  from_ground_map sub_from_ground expr"

sublocale grounding where 
  vars = vars and subst = subst and to_ground = to_ground and from_ground = from_ground
proof unfold_locales
  have "expr. vars expr = {}  expr  range from_ground"
  proof-
    fix expr
    assume "vars expr = {}"
    then have "subto_set expr. sub  range sub_from_ground"
      by (simp add: sub.is_ground_iff_range_from_ground vars_def)

    then have "subto_set expr. sub_ground. sub_from_ground sub_ground = sub"
      by fast

    then have "ground_expr. from_ground ground_expr = expr"
      using map_comp'[symmetric] map_id_cong
      unfolding from_ground_def comp_def
      by metis

    then show "expr  range from_ground"
      unfolding from_ground_def
      by blast
  qed

  moreover have "expr x. x  vars (from_ground expr)  False"
  proof-
    fix expr x
    assume "x  vars (from_ground expr)"
    then show False
      unfolding vars_def from_ground_def
      using sub.ground_is_ground to_set_from_ground_map by auto
  qed

  ultimately show "{f. vars f = {}} = range from_ground"
    by blast   
next
  show "g. to_ground (from_ground g) = g"
    using ground_map_id
    unfolding to_ground_def from_ground_def ground_map_comp sub.to_ground_from_ground_id.
qed

lemma to_set_from_ground: "to_set (from_ground expr) = sub_from_ground ` (to_set_ground expr)"
  unfolding from_ground_def
  by (simp add: to_set_from_ground_map)

lemma sub_in_ground_is_ground: 
  assumes "sub  to_set (from_ground expr)" 
  shows "sub.is_ground sub"
  using assms
  by (simp add: to_set_is_ground)

lemma ground_sub_in_ground: 
  "sub  to_set_ground expr  sub_from_ground sub  to_set (from_ground expr)"
  by (simp add: inj_image_mem_iff sub.inj_from_ground to_set_from_ground)

lemma ground_sub: 
  "(sub  to_set (from_ground exprG). P sub)  
   (subG  to_set_ground exprG. P (sub_from_ground subG))"
  by (simp add: to_set_from_ground)

end

locale all_subst_ident_iff_ground_lifting = 
  finite_variables_lifting +  
  sub: all_subst_ident_iff_ground where subst = sub_subst and is_ground = sub.is_ground
begin

sublocale all_subst_ident_iff_ground 
  where subst = subst and is_ground = is_ground
proof unfold_locales
  show "x. is_ground x = (σ. subst x σ = x)"
  proof(rule iffI allI)
    show "x. is_ground x  σ. subst x σ = x"
      by simp
  next
    fix d x
    assume all_subst_ident: "σ. subst d σ = d"
    
    show "is_ground d"
    proof(rule ccontr)
      assume "¬is_ground d"

      then obtain c where c_in_d: "c  to_set d" and c_not_ground: "¬sub.is_ground c"
        unfolding vars_def
        by blast

      then obtain σ where "sub_subst c σ  c" and "sub_subst c σ  to_set d"
        using sub.exists_non_ident_subst finite_to_set
        by blast
        
      then show False
        using all_subst_ident c_in_d to_set_map
        unfolding subst_def 
        by (metis image_eqI)
    qed
  qed
next
  fix d :: 'd and ds :: "'d set"
  assume finite_ds: "finite ds" and d_not_ground: "¬is_ground d"

  then have finite_cs: "finite ((to_set ` insert d ds))"
    using finite_to_set by blast

  obtain c where c_in_d: "c  to_set d" and c_not_ground: "¬sub.is_ground c"
    using d_not_ground
    unfolding vars_def
    by blast

  obtain σ where σ_not_ident: "sub_subst c σ  c" "sub_subst c σ   (to_set ` insert d ds)"
    using sub.exists_non_ident_subst[OF finite_cs c_not_ground]
    by blast

  then have "subst d σ  d"
    using c_in_d
    unfolding subst_def
    by (simp add: to_set_map_not_ident)

  moreover have "subst d σ  ds"
    using σ_not_ident(2) c_in_d to_set_map
    unfolding subst_def
    by auto

  ultimately show "σ. subst d σ  d  subst d σ  ds"
    by blast
qed

end

end