/-
Copyright (c) 2020 Robert Y. Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Y. Lewis
-/

import data.int.cast.lemmas           -- used by clients
import data.int.char_zero
import tactic.norm_cast

/-!
# A tactic to shift `ℕ` goals to `ℤ`

It is often easier to work in `ℤ`, where subtraction is well behaved, than in `ℕ` where it isn't.
`zify` is a tactic that casts goals and hypotheses about natural numbers to ones about integers.
It makes use of `push_cast`, part of the `norm_cast` family, to simplify these goals.

## Implementation notes

`zify` is extensible, using the attribute `@[zify]` to label lemmas used for moving propositions
from `ℕ` to `ℤ`.
`zify` lemmas should have the form `∀ a₁ ... aₙ : ℕ, Pz (a₁ : ℤ) ... (aₙ : ℤ) ↔ Pn a₁ ... aₙ`.
For example, `int.coe_nat_le_coe_nat_iff : ∀ (m n : ℕ), ↑m ≤ ↑n ↔ m ≤ n` is a `zify` lemma.

`zify` is very nearly just `simp only with zify push_cast`. There are a few minor differences:
* `zify` lemmas are used in the opposite order of the standard simp form.
  E.g. we will rewrite with `int.coe_nat_le_coe_nat_iff` from right to left.
* `zify` should fail if no `zify` lemma applies (i.e. it was unable to shift any proposition to ℤ).
  However, once this succeeds, it does not necessarily need to rewrite with any `push_cast` rules.
-/

open tactic

namespace zify

/--
The `zify` attribute is used by the `zify` tactic. It applies to lemmas that shift propositions
between `nat` and `int`.

`zify` lemmas should have the form `∀ a₁ ... aₙ : ℕ, Pz (a₁ : ℤ) ... (aₙ : ℤ) ↔ Pn a₁ ... aₙ`.
For example, `int.coe_nat_le_coe_nat_iff : ∀ (m n : ℕ), ↑m ≤ ↑n ↔ m ≤ n` is a `zify` lemma.
-/
@[user_attribute]
meta def zify_attr : user_attribute simp_lemmas unit :=
{ name := `zify,
  descr := "Used to tag lemmas for use in the `zify` tactic",
  cache_cfg :=
    { mk_cache :=
        λ ns, mmap (λ n, do c ← mk_const n, return (c, tt)) ns >>= simp_lemmas.mk.append_with_symm,
      dependencies := [] } }

/--
Given an expression `e`, `lift_to_z e` looks for subterms of `e` that are propositions "about"
natural numbers and change them to propositions about integers.

Returns an expression `e'` and a proof that `e = e'`.

Includes `ge_iff_le` and `gt_iff_lt` in the simp set. These can't be tagged with `zify` as we
want to use them in the "forward", not "backward", direction.
-/
meta def lift_to_z (e : expr) : tactic (expr × expr) :=
do sl ← zify_attr.get_cache,
   sl ← sl.add_simp `ge_iff_le, sl ← sl.add_simp `gt_iff_lt,
   (e', prf, _) ← simplify sl [] e,
   return (e', prf)

attribute [zify] int.coe_nat_le_coe_nat_iff int.coe_nat_lt_coe_nat_iff int.coe_nat_eq_coe_nat_iff

end zify

@[zify] lemma int.coe_nat_ne_coe_nat_iff (a b : ℕ) : (a : ℤ) ≠ b ↔ a ≠ b :=
by simp

/--
`zify extra_lems e` is used to shift propositions in `e` from `ℕ` to `ℤ`.
This is often useful since `ℤ` has well-behaved subtraction.

The list of extra lemmas is used in the `push_cast` step.

Returns an expression `e'` and a proof that `e = e'`.-/
meta def tactic.zify (extra_lems : list simp_arg_type) : expr → tactic (expr × expr) := λ z,
do (z1, p1) ← zify.lift_to_z z <|> fail "failed to find an applicable zify lemma",
   (z2, p2) ← norm_cast.derive_push_cast extra_lems z1,
   prod.mk z2 <$> mk_eq_trans p1 p2

/--
A variant of `tactic.zify` that takes `h`, a proof of a proposition about natural numbers,
and returns a proof of the zified version of that propositon.
-/
meta def tactic.zify_proof (extra_lems : list simp_arg_type) (h : expr) : tactic expr :=
do (_, pf) ← infer_type h >>= tactic.zify extra_lems,
   mk_eq_mp pf h

section

setup_tactic_parser

/--
The `zify` tactic is used to shift propositions from `ℕ` to `ℤ`.
This is often useful since `ℤ` has well-behaved subtraction.

```lean
example (a b c x y z : ℕ) (h : ¬ x*y*z < 0) : c < a + 3*b :=
begin
  zify,
  zify at h,
  /-
  h : ¬↑x * ↑y * ↑z < 0
  ⊢ ↑c < ↑a + 3 * ↑b
  -/
end
```

`zify` can be given extra lemmas to use in simplification. This is especially useful in the
presence of nat subtraction: passing `≤` arguments will allow `push_cast` to do more work.
```
example (a b c : ℕ) (h : a - b < c) (hab : b ≤ a) : false :=
begin
  zify [hab] at h,
  /- h : ↑a - ↑b < ↑c -/
end
```

`zify` makes use of the `@[zify]` attribute to move propositions,
and the `push_cast` tactic to simplify the `ℤ`-valued expressions.

`zify` is in some sense dual to the `lift` tactic. `lift (z : ℤ) to ℕ` will change the type of an
integer `z` (in the supertype) to `ℕ` (the subtype), given a proof that `z ≥ 0`;
propositions concerning `z` will still be over `ℤ`. `zify` changes propositions about `ℕ` (the
subtype) to propositions about `ℤ` (the supertype), without changing the type of any variable.
-/
meta def tactic.interactive.zify (sl : parse simp_arg_list) (l : parse location) : tactic unit :=
do locs ← l.get_locals,
replace_at (tactic.zify sl) locs l.include_goal >>= guardb

end

add_tactic_doc
{ name := "zify",
  category := doc_category.attr,
  decl_names := [`zify.zify_attr],
  tags := ["coercions", "transport"] }

add_tactic_doc
{ name := "zify",
  category := doc_category.tactic,
  decl_names := [`tactic.interactive.zify],
  tags := ["coercions", "transport"] }


/-
Copyright (c) 2022 Jun Yoshida. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
-/

import Dijkstra.Control.Monad.Rel

/-!

# Specification Monads

(based on [Dijkstra Mondads for All](https://arxiv.org/abs/1903.01237) by K. Maillard, D. Ahman, R. Atkey, G. Martinez, C. Hritcu, E. Rivas, É. Tanter)

A ***specification*** monad is a monad that is used to represent pre/post-conditions of computation.
It is usually associated to a specific monad that provides actual computation semantics.

The idea is based on the notion of ***predicate transformer*** introduced by Dijkstra in the paper *"Guarded commands, nondeterminacy and formal derivation of programs"*.
For example, a specification monad `WPPure` for the monad `Id`, which is the "pure computation", is as follows:

```lean
WPPure α ≡ {wp : (α → Prop) → Prop // ∀ (p q : α → Prop), (∀ a, p a → q a) → wp p → wp q}
```

Indeed, it (coherently) transforms a post-condition `p : α → Prop` into the *weakest precondition* `wp p : Prop` which ensures `p` after computation.

In spite of the origin of the notion, not all specification monads are of this form.
Actually, we call a monad `m` a specification monad provided it is equipped with a ***monadic relation***; e.g. a relation on each `m α` which is respected by `pure` and `bind`.
For example, the specification monad `WPPure` above has a canonical order so that, for `x y : WPPure α`,

```lean
x ≤ y ≡ ∀ (p : α → Prop), y p → x p
```

In other words, `x ≤ y` means that `y` requires a stronger pre-condition than `x` does.
For more details on monadic relations, see [Rel.lean](Dijkstra/Control/Monad/Rel.lean).

-/

universe u v w

/-!

## Definition of specification monads

-/

/-- `SpecMonad` is a monad which is equipped with a transitive monadic relation. -/
class SpecMonad (m : Type u → Type v) [Monad m] where
  rel : MonadRel m m
  trans {α : Type u} {x y z : m α} : rel.rel x y → rel.rel y z → rel.rel x z

def mrel {m : Type u → Type v} [Monad m] [self : SpecMonad m] {α : Type u} : m α → m α → Prop :=
  self.rel.rel

theorem mrel_pure {m : Type u → Type v} [Monad m] [self : SpecMonad m] {α : Type u} (a : α) : mrel (m:=m) (pure a) (pure a) :=
  self.rel.pure a

theorem mrel_bind {m : Type u → Type v} [Monad m] [self : SpecMonad m] {α β : Type u} {x y : m α} {f g : α → m β} : mrel x y → (∀ a, mrel (f a) (g a)) → mrel (x >>= f) (y >>= g) :=
  self.rel.bind

theorem mrel_trans {m : Type u → Type v} [Monad m] [self : SpecMonad m] {α : Type u} {x y z : m α} : mrel x y → mrel y z → mrel x z :=
  self.trans

instance {m : Type u → Type v} [Monad m] [SpecMonad m] {α : Type u} : Trans (mrel (m:=m) (α:=α)) (mrel (m:=m)) (mrel (m:=m)) where
  trans := mrel_trans

/-!

## Examples of specification monads

-/

/-!
### Predicate monad

For `α : Type u`, `Pred α = α → Prop` is the type of predicates on the terms of `α`.
As it is identified with the set of subtypes of `α`, `Pred` can also be seen as the (covariant) power-set operator and hence admits a canonical structure of a monad.
The `pure : α → Pred α` forms a singleton subtype, and the `bind : Pred α → (α → Pred β) → Pred β` takes the union of an `α`-indexed subtype-family over a subset.
In terms of predicates, `(p >>= q) b` holds precisely if there is a term `a : α` such that both `p a` and `q a b` are satisfied.

-/

def Pred (α : Type u) : Type u := α → Prop

/-- Equality of `Pred α` from `funext` and `propext`. -/
protected
def Pred.ext {α : Type u} {p q : Pred α} : (∀ a, p a ↔ q a) → p = q :=
  λ hpq => funext (λ a => propext (hpq a))

instance instMonadPred : Monad Pred where
  pure a := Eq a
  bind p f := λ b => ∃ a, p a ∧ f a b

instance instLawfulMonadPred : LawfulMonad Pred where
  map_const := rfl
  id_map p := by
    funext a; dsimp [Functor.map]
    apply propext
    constructor
    case mp =>
      intro h; cases h with | intro a ha => exact ha.2 ▸ ha.1
    case mpr =>
      intro h; exists a
  seqLeft_eq p q := by
    dsimp [Seq.seq, SeqLeft.seqLeft, Functor.map]
    funext a; apply propext
    constructor
    case mp =>
      intro h; cases h with | intro a₁ ha₁ =>
      exists (λ _ => a₁)
      exact ⟨Exists.intro a₁ ⟨ha₁.left, rfl⟩, ha₁.right⟩
    case mpr =>
      intro h; cases h with | intro f hf =>
      cases hf.left with | intro a₁ ha₁ =>
      rw [←ha₁.right] at hf
      exact Exists.intro a₁ $ ⟨ha₁.left, hf.right⟩
  seqRight_eq p q := by
    dsimp [SeqRight.seqRight, Seq.seq, Functor.map]
    funext b; apply propext
    constructor
    case mp =>
      intro h; cases h with | intro a ha =>
      exists id
      exact And.intro ⟨a, ⟨ha.left, rfl⟩⟩ ⟨b, ⟨ha.right, rfl⟩⟩
    case mpr =>
      intro h; cases h with | intro f hf =>
      cases hf.left with | intro a ha =>
      cases hf.right with | intro b₁ hb₁ =>
      cases hb₁.right
      rw [←ha.right] at *
      exact ⟨a, ⟨ha.left, hb₁.left⟩⟩
  pure_seq f p := by
    dsimp [Seq.seq, Pure.pure, Functor.map]
    funext b; apply propext
    constructor
    case mp =>
      intro h; cases h with | intro f₁ hf₁ =>
      cases hf₁.left.symm
      exact hf₁.right
    case mpr =>
      intro h; exact ⟨f, ⟨rfl, h⟩⟩
  bind_pure_comp f p := rfl
  bind_map p q := rfl
  pure_bind a f := by
    dsimp [pure, bind]
    funext b; apply propext
    constructor
    case mp =>
      intro h; cases h with | intro a ha =>
      rw [ha.left]; exact ha.right
    case mpr =>
      intro hf
      exact ⟨a, ⟨rfl, hf⟩⟩
  bind_assoc p f g := by
    dsimp [bind]
    funext b; apply propext
    constructor
    case mp =>
      intro h; cases h with | intro b₁ hb₁ =>
      cases hb₁.left with | intro a ha =>
      exists a; apply And.intro ha.left ⟨b₁, ⟨ha.right, hb₁.right⟩⟩
    case mpr =>
      intro h; cases h with | intro a ha =>
      cases ha.right with | intro b₁ hb₁ =>
      exists b₁
      exact And.intro ⟨a, ⟨ha.left, hb₁.left⟩⟩ hb₁.right

/-- The power-set `Pred α` is ordered by inclusion. In terms of predicates, the order agrees with the "implication". -/
protected
def Pred.rel : MonadRel Pred Pred where
  rel p q := ∀ a, p a → q a
  pure a := by dsimp [Pure.pure]; intro _; exact id
  bind := by
    intro _ _ p q f g hpq hfg b hb
    dsimp [bind] at *
    cases hb with | intro a ha =>
    exists a
    exact ⟨hpq a ha.left, hfg a b ha.right⟩

instance instSpecMonad : SpecMonad Pred where
  rel := Pred.rel
  trans hpq hqr := λ a hp => hqr a (hpq a hp)

/-!
### Weakest predicate for pure computations
-/

structure WPPure (α : Type u) where
  predT : (α → Prop) → Prop
  monotonic {p q : α → Prop} (h : ∀ a, p a → q a) : predT p → predT q

instance coeWPPurePredT (α :Type u) : CoeFun (WPPure α) (λ _ => (α → Prop) → Prop) where
  coe wp := wp.predT

/-- A utility lemma to prove equality on `WPPure α`. -/
protected
theorem WPPure.eq {α : Type u} : ∀ {wp wq : WPPure α}, wp.predT = wq.predT → wp = wq
| mk _ _, mk _ _, rfl => rfl

/-- Equality on `WPPure α` from `funext` and `propext`. -/
protected
theorem WPPure.ext {α : Type u} {wp wq : WPPure α} : (∀ (p : α → Prop), wp.predT p ↔ wq.predT p) → wp = wq :=
  λ hpq => WPPure.eq $ funext λ p => propext (hpq p)

instance instMonadWPPure : Monad WPPure where
  pure a := {
    predT := λ p => p a
    monotonic := by
      intro p q hpq hpa
      exact hpq a hpa
  }
  bind x f := {
    predT := λ p => x.predT (λ a => (f a).predT p)
    monotonic := by
      intro p q hpq hxp
      exact x.monotonic (λ a => (f a).monotonic hpq) hxp
  }

instance instLawfulMonadWPPure : LawfulMonad WPPure where
  map_const := rfl
  id_map _ := rfl
  seqLeft_eq _ _ := rfl
  seqRight_eq _ _ := rfl
  pure_seq _ _ := rfl
  bind_pure_comp _ _ := rfl
  bind_map _ _ := rfl
  pure_bind _ _ := rfl
  bind_assoc _ _ _ := rfl

protected
def WPPure.rel : MonadRel WPPure WPPure where
  rel x y := ∀ p, y.predT p → x.predT p
  pure a := by dsimp; intro _; exact id
  bind := by
    dsimp [bind]; intro _ _ x y f g hxy hfg hb p
    apply hxy
    exact y.monotonic (λ a => hfg a hb) p

instance instSpecMonadWPPure : SpecMonad WPPure where
  rel := WPPure.rel
  trans hxy hyz := λ p hz => hxy p (hyz p hz)

/-!

## Relations between specification monads

For monadic relations between specification monads, we assume compatibility with the intrinsic relations in addition to compatibility with monadic structures.

-/

structure SpecMonadRel (m : Type u → Type v) [Monad m] [SpecMonad m] (n : Type u → Type w) [Monad n] [SpecMonad n] extends MonadRel m n where
  trans_left {α : Type u} {mx my : m α} {nz : n α} : mrel mx my → rel my nz → rel mx nz
  trans_right {α : Type u} {mx : m α} {ny nz : n α} : rel mx ny → mrel ny nz → rel mx nz

def WPPure.validate : SpecMonadRel WPPure Pred where
  rel wp p := wp.predT p
  pure a := rfl
  bind {α} {β} wa pa wf pf hwp hf := by
    dsimp [bind] at *
    apply wa.monotonic (p:=pa) _ hwp
    intro a ha
    apply (wf a).monotonic _ (hf a)
    intro b hb
    exact ⟨a, ⟨ha, hb⟩⟩
  trans_left := by
    dsimp [mrel, SpecMonad.rel, WPPure.rel]
    intro α wx wy p hxy hp
    exact hxy p hp
  trans_right := by
    dsimp [mrel, SpecMonad.rel, Pred.rel]
    intro α wx p q hp hpq
    exact wx.monotonic hpq hp


import .logic .comb
open set classical

namespace finset

variable {A : Type}
variable [deceq : decidable_eq A]

definition to_set (s : finset A) : set A := λx, x ∈ s

-- def ts := @to_set A
instance finset_to_set_coe : has_coe (finset A) (set A) := ⟨to_set⟩

variables (s t : finset A) (x y : A)

theorem mem_eq_mem_to_set : x ∈ s = (x ∈ to_set s) := rfl

definition to_set.inj {s₁ s₂ : finset A} : to_set s₁ = to_set s₂ → s₁ = s₂ :=
λ h, ext (λ a, iff.of_eq (calc
  (a ∈ s₁) = (a ∈ to_set s₁) : mem_eq_mem_to_set _ _
       ... = (a ∈ to_set s₂) : by rw -h
       ... = (a ∈ s₂) : mem_eq_mem_to_set _ _))

-- A example
theorem mem_to_set_empty : (set.mem x (to_set empty)) = (mem x empty) := rfl
theorem to_set_empty : to_set empty = (∅:set A) := rfl

-- theorem mem_to_set_univ [h : fintype A] : (x ∈ to_set univ) = (x ∈ set.univ) :=
--   propext (iff.intro (assume H, trivial) (assume H, !mem_univ))
-- theorem to_set_univ [h : fintype A] : to_set univ = (set.univ : set A) := funext (λ x, !mem_to_set_univ)

-- instance : has_subset (finset A) := ⟨finset.subset⟩

theorem mem_to_set_upto (x n : ℕ) : x ∈ to_set (upto n) = (x ∈ {a | a < n}) := mem_upto_eq _ _
theorem to_set_upto (n : ℕ) : to_set (upto n) = {a | a < n} := funext (λ x, mem_to_set_upto _ _)

include deceq

theorem mem_to_set_insert : x ∈ insert y s = (x ∈ set.insert y s) := mem_insert_eq _ _ _

theorem to_set_insert : (set.insert y (to_set s)) = to_set (insert y s) := funext (λ x, eq.symm (mem_to_set_insert _ _ _))

theorem mem_to_set_union : x ∈ s ∪ t = (x ∈ to_set s ∪ to_set t) := mem_union_eq _ _ _
theorem to_set_union : to_set (s ∪ t) = to_set s ∪ to_set t := funext (λ x, mem_to_set_union _ _ _)

theorem mem_to_set_inter : x ∈ s ∩ t = (x ∈ to_set s ∩ to_set t) := mem_inter_eq _ _ _
theorem to_set_inter : to_set (s ∩ t) = to_set s ∩ to_set t := funext (λ x, mem_to_set_inter _ _ _)

theorem mem_to_set_diff : x ∈ s \ t = (x ∈ to_set s \ to_set t) := mem_diff_eq _ _ _
theorem to_set_diff : to_set (s \ t) = to_set s \ to_set t := funext (λ x, mem_to_set_diff _ _ _)

theorem mem_to_set_sep (p : A → Prop) [h : decidable_pred p] : x ∈ sep p s = (x ∈ set.sep p s) :=
  finset.mem_sep_eq _ _
theorem to_set_sep (p : A → Prop) [h : decidable_pred p] : to_set (sep p s) = set.sep p (to_set s) :=
  funext (λ x, mem_to_set_sep _ _ _)

theorem mem_to_set_image {B : Type} [h : decidable_eq B] (f : A → B) {s : finset A} {y : B} :
  y ∈ image f s = (y ∈ set.image f s) := mem_image_eq _
theorem to_set_image {B : Type} [h : decidable_eq B] (f : A → B) (s : finset A) :
to_set (image f s) = set.image f (to_set s) := funext (λ x, mem_to_set_image _)

/- relations -/

attribute [instance]
definition decidable_mem_to_set (x : A) (s : finset A) : decidable (x ∈ to_set s) :=
decidable_of_decidable_of_eq (decidable_mem _ _) (mem_eq_mem_to_set _ _)

theorem eq_of_to_set_eq_to_set {s t : finset A} (H : to_set s =  to_set t) : s = t :=
to_set.inj H

theorem eq_eq_to_set_eq : (s = t) = (to_set s = to_set t) :=
propext (iff.intro (assume H, H ▸ rfl) eq_of_to_set_eq_to_set)

-- #check quot.has_decidable_eq

attribute [instance]
definition decidable_to_set_eq (s t : finset A) : decidable (to_set s = to_set t) :=
decidable_of_decidable_of_eq (has_decidable_eq _ _) (eq_eq_to_set_eq _ _)

theorem subset_eq_to_set_subset (s t : finset A) : (s ⊆ t) = (to_set s ⊆ to_set t) :=
propext (iff.intro
  (assume H, take x xs, mem_of_subset_of_mem H xs)
  (assume H, subset_of_forall H))

-- attribute [instance]
-- definition decidable_subset [h : decidable_eq A] : ∀ (s t: finset A), decidable (s ⊆ t) :=
-- sorry

-- definition decidable_to_set_subset (s t : finset A) : decidable (to_set s ⊆ to_set t) :=
-- decidable_of_decidable_of_eq (decidable_subset _ _) (subset_eq_to_set_subset _ _)

/- bounded quantifiers -/

-- definition decidable_bounded_forall (s : finset A) (p : A → Prop) [h : decidable_pred p] :
--   decidable (∀ x ∈ to_set s, p x) :=
-- decidable_of_decidable_of_iff _ !all_iff_forall

-- definition decidable_bounded_existo_set (s : finset A) (p : A → Prop) [h : decidable_pred p] :
--   decidable (∃₀ x ∈ to_set s, p x) :=
-- decidable_of_decidable_of_iff _ !any_iff_exists

/- properties -/

-- theorem inj_on_to_set {B : Type} [h : decidable_eq B] (f : A → B) (s : finset A) :
--   inj_on f s = inj_on f (to_set s) :=
-- rfl

end finset


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import category_theory.limits.preserves.basic

/-!
# Isomorphisms about functors which preserve (co)limits

If `G` preserves limits, and `C` and `D` have limits, then for any diagram `F : J ⥤ C` we have a
canonical isomorphism `preserves_limit_iso : G.obj (limit F) ≅ limit (F ⋙ G)`.
We also show that we can commute `is_limit.lift` of a preserved limit with `functor.map_cone`:
`(preserves_limit.preserves t).lift (G.map_cone c₂) = G.map (t.lift c₂)`.

The duals of these are also given. For functors which preserve (co)limits of specific shapes, see
`preserves/shapes.lean`.
-/

universes w' w v₁ v₂ u₁ u₂

noncomputable theory

namespace category_theory

open category limits

variables {C : Type u₁} [category.{v₁} C]
variables {D : Type u₂} [category.{v₂} D]
variables (G : C ⥤ D)
variables {J : Type w} [category.{w'} J]
variables (F : J ⥤ C)

section
variables [preserves_limit F G]

@[simp]
lemma preserves_lift_map_cone (c₁ c₂ : cone F) (t : is_limit c₁) :
  (preserves_limit.preserves t).lift (G.map_cone c₂) = G.map (t.lift c₂) :=
((preserves_limit.preserves t).uniq (G.map_cone c₂) _ (by simp [← G.map_comp])).symm

variables [has_limit F] [has_limit (F ⋙ G)]
/--
If `G` preserves limits, we have an isomorphism from the image of the limit of a functor `F`
to the limit of the functor `F ⋙ G`.
-/
def preserves_limit_iso : G.obj (limit F) ≅ limit (F ⋙ G) :=
(preserves_limit.preserves (limit.is_limit _)).cone_point_unique_up_to_iso (limit.is_limit _)

@[simp, reassoc]
lemma preserves_limits_iso_hom_π (j) :
  (preserves_limit_iso G F).hom ≫ limit.π _ j = G.map (limit.π F j) :=
is_limit.cone_point_unique_up_to_iso_hom_comp _ _ j

@[simp, reassoc]
lemma preserves_limits_iso_inv_π (j) :
  (preserves_limit_iso G F).inv ≫ G.map (limit.π F j) = limit.π _ j :=
is_limit.cone_point_unique_up_to_iso_inv_comp _ _ j

@[simp, reassoc]
lemma lift_comp_preserves_limits_iso_hom (t : cone F) :
  G.map (limit.lift _ t) ≫ (preserves_limit_iso G F).hom = limit.lift (F ⋙ G) (G.map_cone _) :=
by { ext, simp [← G.map_comp] }

variables [preserves_limits_of_shape J G] [has_limits_of_shape J D] [has_limits_of_shape J C]

/-- If `C, D` has all limits of shape `J`, and `G` preserves them, then `preserves_limit_iso` is
functorial wrt `F`. -/
@[simps] def preserves_limit_nat_iso : lim ⋙ G ≅ (whiskering_right J C D).obj G ⋙ lim :=
nat_iso.of_components (λ F, preserves_limit_iso G F)
begin
  intros _ _ f,
  ext,
  dsimp,
  simp only [preserves_limits_iso_hom_π, whisker_right_app, lim_map_π, category.assoc,
    preserves_limits_iso_hom_π_assoc, ← G.map_comp]
end

end

section
variables [preserves_colimit F G]

@[simp]
lemma preserves_desc_map_cocone (c₁ c₂ : cocone F) (t : is_colimit c₁) :
  (preserves_colimit.preserves t).desc (G.map_cocone _) = G.map (t.desc c₂) :=
((preserves_colimit.preserves t).uniq (G.map_cocone _) _ (by simp [← G.map_comp])).symm

variables [has_colimit F] [has_colimit (F ⋙ G)]
/--
If `G` preserves colimits, we have an isomorphism from the image of the colimit of a functor `F`
to the colimit of the functor `F ⋙ G`.
-/
-- TODO: think about swapping the order here
def preserves_colimit_iso : G.obj (colimit F) ≅ colimit (F ⋙ G) :=
(preserves_colimit.preserves (colimit.is_colimit _)).cocone_point_unique_up_to_iso
  (colimit.is_colimit _)

@[simp, reassoc]
lemma ι_preserves_colimits_iso_inv (j : J) :
  colimit.ι _ j ≫ (preserves_colimit_iso G F).inv = G.map (colimit.ι F j) :=
is_colimit.comp_cocone_point_unique_up_to_iso_inv _ (colimit.is_colimit (F ⋙ G)) j

@[simp, reassoc]
lemma ι_preserves_colimits_iso_hom (j : J) :
  G.map (colimit.ι F j) ≫ (preserves_colimit_iso G F).hom = colimit.ι (F ⋙ G) j :=
(preserves_colimit.preserves (colimit.is_colimit _)).comp_cocone_point_unique_up_to_iso_hom _ j

@[simp, reassoc]
lemma preserves_colimits_iso_inv_comp_desc (t : cocone F) :
  (preserves_colimit_iso G F).inv ≫ G.map (colimit.desc _ t) = colimit.desc _ (G.map_cocone t) :=
by { ext, simp [← G.map_comp] }

variables [preserves_colimits_of_shape J G] [has_colimits_of_shape J D] [has_colimits_of_shape J C]

/-- If `C, D` has all colimits of shape `J`, and `G` preserves them, then `preserves_colimit_iso`
is functorial wrt `F`. -/
@[simps] def preserves_colimit_nat_iso : colim ⋙ G ≅ (whiskering_right J C D).obj G ⋙ colim :=
nat_iso.of_components (λ F, preserves_colimit_iso G F)
begin
  intros _ _ f,
  rw [← iso.inv_comp_eq, ← category.assoc, ← iso.eq_comp_inv],
  ext,
  dsimp,
  erw ι_colim_map_assoc,
  simp only [ι_preserves_colimits_iso_inv, whisker_right_app, category.assoc,
    ι_preserves_colimits_iso_inv_assoc, ← G.map_comp],
  erw ι_colim_map
end

end

end category_theory


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import category_theory.limits.shapes.split_coequalizer
import category_theory.limits.preserves.basic

/-!
# Preserving (co)equalizers

Constructions to relate the notions of preserving (co)equalizers and reflecting (co)equalizers
to concrete (co)forks.

In particular, we show that `equalizer_comparison f g G` is an isomorphism iff `G` preserves
the limit of the parallel pair `f,g`, as well as the dual result.
-/

noncomputable theory

universes w v₁ v₂ u₁ u₂

open category_theory category_theory.category category_theory.limits

variables {C : Type u₁} [category.{v₁} C]
variables {D : Type u₂} [category.{v₂} D]
variables (G : C ⥤ D)

namespace category_theory.limits

section equalizers
variables {X Y Z : C} {f g : X ⟶ Y} {h : Z ⟶ X} (w : h ≫ f = h ≫ g)

/--
The map of a fork is a limit iff the fork consisting of the mapped morphisms is a limit. This
essentially lets us commute `fork.of_ι` with `functor.map_cone`.
-/
def is_limit_map_cone_fork_equiv :
  is_limit (G.map_cone (fork.of_ι h w)) ≃
  is_limit (fork.of_ι (G.map h) (by simp only [←G.map_comp, w]) : fork (G.map f) (G.map g)) :=
(is_limit.postcompose_hom_equiv (diagram_iso_parallel_pair _) _).symm.trans
  (is_limit.equiv_iso_limit (fork.ext (iso.refl _) (by { simp [fork.ι] })))

/-- The property of preserving equalizers expressed in terms of forks. -/
def is_limit_fork_map_of_is_limit [preserves_limit (parallel_pair f g) G]
  (l : is_limit (fork.of_ι h w)) :
  is_limit (fork.of_ι (G.map h) (by simp only [←G.map_comp, w]) : fork (G.map f) (G.map g)) :=
is_limit_map_cone_fork_equiv G w (preserves_limit.preserves l)

/-- The property of reflecting equalizers expressed in terms of forks. -/
def is_limit_of_is_limit_fork_map [reflects_limit (parallel_pair f g) G]
  (l : is_limit (fork.of_ι (G.map h) (by simp only [←G.map_comp, w]) : fork (G.map f) (G.map g))) :
  is_limit (fork.of_ι h w) :=
reflects_limit.reflects ((is_limit_map_cone_fork_equiv G w).symm l)

variables (f g) [has_equalizer f g]

/--
If `G` preserves equalizers and `C` has them, then the fork constructed of the mapped morphisms of
a fork is a limit.
-/
def is_limit_of_has_equalizer_of_preserves_limit
  [preserves_limit (parallel_pair f g) G] :
  is_limit (fork.of_ι (G.map (equalizer.ι f g))
                      (by simp only [←G.map_comp, equalizer.condition])) :=
is_limit_fork_map_of_is_limit G _ (equalizer_is_equalizer f g)

variables [has_equalizer (G.map f) (G.map g)]

/--
If the equalizer comparison map for `G` at `(f,g)` is an isomorphism, then `G` preserves the
equalizer of `(f,g)`.
-/
def preserves_equalizer.of_iso_comparison [i : is_iso (equalizer_comparison f g G)] :
  preserves_limit (parallel_pair f g) G :=
begin
  apply preserves_limit_of_preserves_limit_cone (equalizer_is_equalizer f g),
  apply (is_limit_map_cone_fork_equiv _ _).symm _,
  apply is_limit.of_point_iso (limit.is_limit (parallel_pair (G.map f) (G.map g))),
  apply i,
end

variables [preserves_limit (parallel_pair f g) G]
/--
If `G` preserves the equalizer of `(f,g)`, then the equalizer comparison map for `G` at `(f,g)` is
an isomorphism.
-/
def preserves_equalizer.iso :
  G.obj (equalizer f g) ≅ equalizer (G.map f) (G.map g) :=
is_limit.cone_point_unique_up_to_iso
  (is_limit_of_has_equalizer_of_preserves_limit G f g)
  (limit.is_limit _)

@[simp]
lemma preserves_equalizer.iso_hom :
  (preserves_equalizer.iso G f g).hom = equalizer_comparison f g G :=
rfl

instance : is_iso (equalizer_comparison f g G) :=
begin
  rw ← preserves_equalizer.iso_hom,
  apply_instance
end

end equalizers

section coequalizers

variables {X Y Z : C} {f g : X ⟶ Y} {h : Y ⟶ Z} (w : f ≫ h = g ≫ h)

/--
The map of a cofork is a colimit iff the cofork consisting of the mapped morphisms is a colimit.
This essentially lets us commute `cofork.of_π` with `functor.map_cocone`.
-/
def is_colimit_map_cocone_cofork_equiv :
  is_colimit (G.map_cocone (cofork.of_π h w)) ≃
  is_colimit (cofork.of_π (G.map h) (by simp only [←G.map_comp, w]) : cofork (G.map f) (G.map g)) :=
(is_colimit.precompose_inv_equiv (diagram_iso_parallel_pair _) _).symm.trans $
is_colimit.equiv_iso_colimit $ cofork.ext (iso.refl _) $
begin
  dsimp only [cofork.π, cofork.of_π_ι_app],
  dsimp, rw [category.comp_id, category.id_comp]
end

/-- The property of preserving coequalizers expressed in terms of coforks. -/
def is_colimit_cofork_map_of_is_colimit [preserves_colimit (parallel_pair f g) G]
  (l : is_colimit (cofork.of_π h w)) :
  is_colimit (cofork.of_π (G.map h) (by simp only [←G.map_comp, w]) : cofork (G.map f) (G.map g)) :=
is_colimit_map_cocone_cofork_equiv G w (preserves_colimit.preserves l)

/-- The property of reflecting coequalizers expressed in terms of coforks. -/
def is_colimit_of_is_colimit_cofork_map [reflects_colimit (parallel_pair f g) G]
  (l : is_colimit (cofork.of_π (G.map h) (by simp only [←G.map_comp, w])
                  : cofork (G.map f) (G.map g))) :
  is_colimit (cofork.of_π h w) :=
reflects_colimit.reflects ((is_colimit_map_cocone_cofork_equiv G w).symm l)

variables (f g) [has_coequalizer f g]

/--
If `G` preserves coequalizers and `C` has them, then the cofork constructed of the mapped morphisms
of a cofork is a colimit.
-/
def is_colimit_of_has_coequalizer_of_preserves_colimit
  [preserves_colimit (parallel_pair f g) G] :
  is_colimit (cofork.of_π (G.map (coequalizer.π f g)) _) :=
is_colimit_cofork_map_of_is_colimit G _ (coequalizer_is_coequalizer f g)

variables [has_coequalizer (G.map f) (G.map g)]

/--
If the coequalizer comparison map for `G` at `(f,g)` is an isomorphism, then `G` preserves the
coequalizer of `(f,g)`.
-/
def of_iso_comparison [i : is_iso (coequalizer_comparison f g G)] :
  preserves_colimit (parallel_pair f g) G :=
begin
  apply preserves_colimit_of_preserves_colimit_cocone (coequalizer_is_coequalizer f g),
  apply (is_colimit_map_cocone_cofork_equiv _ _).symm _,
  apply is_colimit.of_point_iso (colimit.is_colimit (parallel_pair (G.map f) (G.map g))),
  apply i,
end

variables [preserves_colimit (parallel_pair f g) G]
/--
If `G` preserves the coequalizer of `(f,g)`, then the coequalizer comparison map for `G` at `(f,g)`
is an isomorphism.
-/
def preserves_coequalizer.iso :
  coequalizer (G.map f) (G.map g) ≅ G.obj (coequalizer f g) :=
is_colimit.cocone_point_unique_up_to_iso
  (colimit.is_colimit _)
  (is_colimit_of_has_coequalizer_of_preserves_colimit G f g)

@[simp]
lemma preserves_coequalizer.iso_hom :
  (preserves_coequalizer.iso G f g).hom = coequalizer_comparison f g G :=
rfl

instance : is_iso (coequalizer_comparison f g G) :=
begin
  rw ← preserves_coequalizer.iso_hom,
  apply_instance
end

/-- Any functor preserves coequalizers of split pairs. -/
@[priority 1]
instance preserves_split_coequalizers (f g : X ⟶ Y) [has_split_coequalizer f g] :
  preserves_colimit (parallel_pair f g) G :=
begin
  apply preserves_colimit_of_preserves_colimit_cocone
            ((has_split_coequalizer.is_split_coequalizer f g).is_coequalizer),
  apply (is_colimit_map_cocone_cofork_equiv G _).symm
            ((has_split_coequalizer.is_split_coequalizer f g).map G).is_coequalizer,
end
end coequalizers

end category_theory.limits


/-
Copyright (c) 2021 Aaron Anderson, Jesse Michael Han, Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson, Jesse Michael Han, Floris van Doorn

! This file was ported from Lean 3 source module model_theory.semantics
! leanprover-community/mathlib commit d565b3df44619c1498326936be16f1a935df0728
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Finset.Basic
import Mathbin.ModelTheory.Syntax

/-!
# Basics on First-Order Semantics
This file defines the interpretations of first-order terms, formulas, sentences, and theories
in a style inspired by the [Flypitch project](https://flypitch.github.io/).

## Main Definitions
* `first_order.language.term.realize` is defined so that `t.realize v` is the term `t` evaluated at
variables `v`.
* `first_order.language.bounded_formula.realize` is defined so that `φ.realize v xs` is the bounded
formula `φ` evaluated at tuples of variables `v` and `xs`.
* `first_order.language.formula.realize` is defined so that `φ.realize v` is the formula `φ`
evaluated at variables `v`.
* `first_order.language.sentence.realize` is defined so that `φ.realize M` is the sentence `φ`
evaluated in the structure `M`. Also denoted `M ⊨ φ`.
* `first_order.language.Theory.model` is defined so that `T.model M` is true if and only if every
sentence of `T` is realized in `M`. Also denoted `T ⊨ φ`.

## Main Results
* `first_order.language.bounded_formula.realize_to_prenex` shows that the prenex normal form of a
formula has the same realization as the original formula.
* Several results in this file show that syntactic constructions such as `relabel`, `cast_le`,
`lift_at`, `subst`, and the actions of language maps commute with realization of terms, formulas,
sentences, and theories.

## Implementation Notes
* Formulas use a modified version of de Bruijn variables. Specifically, a `L.bounded_formula α n`
is a formula with some variables indexed by a type `α`, which cannot be quantified over, and some
indexed by `fin n`, which can. For any `φ : L.bounded_formula α (n + 1)`, we define the formula
`∀' φ : L.bounded_formula α n` by universally quantifying over the variable indexed by
`n : fin (n + 1)`.

## References
For the Flypitch project:
- [J. Han, F. van Doorn, *A formal proof of the independence of the continuum hypothesis*]
[flypitch_cpp]
- [J. Han, F. van Doorn, *A formalization of forcing and the unprovability of
the continuum hypothesis*][flypitch_itp]

-/

universe u v w u' v'

namespace FirstOrder

namespace Language

variable {L : Language.{u, v}} {L' : Language}

variable {M : Type w} {N P : Type _} [L.Structure M] [L.Structure N] [L.Structure P]

variable {α : Type u'} {β : Type v'}

open FirstOrder Cardinal

open Structure Cardinal Fin

namespace Term

/- warning: first_order.language.term.realize -> FirstOrder.Language.Term.realize is a dubious translation:
lean 3 declaration is
  forall {L : FirstOrder.Language.{u1, u2}} {M : Type.{u3}} [_inst_1 : FirstOrder.Language.Structure.{u1, u2, u3} L M] {α : Type.{u4}}, (α -> M) -> (FirstOrder.Language.Term.{u1, u2, u4} L α) -> M
but is expected to have type
  forall {L : FirstOrder.Language.{u1, u3}} {M : Type.{u4}} [_inst_1 : FirstOrder.Language.Structure.{u1, u3, u4} L M] {α : Type.{u2}}, (α -> M) -> (FirstOrder.Language.Term.{u1, u3, u2} L α) -> M
Case conversion may be inaccurate. Consider using '#align first_order.language.term.realize FirstOrder.Language.Term.realizeₓ'. -/
/-- A term `t` with variables indexed by `α` can be evaluated by giving a value to each variable. -/
@[simp]
def realize (v : α → M) : ∀ t : L.term α, M
  | var k => v k
  | func f ts => funMap f fun i => (ts i).realize
#align first_order.language.term.realize FirstOrder.Language.Term.realize

@[simp]
theorem realize_relabel {t : L.term α} {g : α → β} {v : β → M} :
    (t.relabel g).realize v = t.realize (v ∘ g) :=
  by
  induction' t with _ n f ts ih
  · rfl
  · simp [ih]
#align first_order.language.term.realize_relabel FirstOrder.Language.Term.realize_relabel

@[simp]
theorem realize_liftAt {n n' m : ℕ} {t : L.term (Sum α (Fin n))} {v : Sum α (Fin (n + n')) → M} :
    (t.liftAt n' m).realize v =
      t.realize (v ∘ Sum.map id fun i => if ↑i < m then Fin.castAdd n' i else Fin.addNat n' i) :=
  realize_relabel
#align first_order.language.term.realize_lift_at FirstOrder.Language.Term.realize_liftAt

@[simp]
theorem realize_constants {c : L.Constants} {v : α → M} : c.term.realize v = c :=
  funMap_eq_coe_constants
#align first_order.language.term.realize_constants FirstOrder.Language.Term.realize_constants

@[simp]
theorem realize_functions_apply₁ {f : L.Functions 1} {t : L.term α} {v : α → M} :
    (f.apply₁ t).realize v = funMap f ![t.realize v] :=
  by
  rw [functions.apply₁, term.realize]
  refine' congr rfl (funext fun i => _)
  simp only [Matrix.cons_val_fin_one]
#align first_order.language.term.realize_functions_apply₁ FirstOrder.Language.Term.realize_functions_apply₁

@[simp]
theorem realize_functions_apply₂ {f : L.Functions 2} {t₁ t₂ : L.term α} {v : α → M} :
    (f.apply₂ t₁ t₂).realize v = funMap f ![t₁.realize v, t₂.realize v] :=
  by
  rw [functions.apply₂, term.realize]
  refine' congr rfl (funext (Fin.cases _ _))
  · simp only [Matrix.cons_val_zero]
  · simp only [Matrix.cons_val_succ, Matrix.cons_val_fin_one, forall_const]
#align first_order.language.term.realize_functions_apply₂ FirstOrder.Language.Term.realize_functions_apply₂

theorem realize_con {A : Set M} {a : A} {v : α → M} : (L.con a).term.realize v = a :=
  rfl
#align first_order.language.term.realize_con FirstOrder.Language.Term.realize_con

@[simp]
theorem realize_subst {t : L.term α} {tf : α → L.term β} {v : β → M} :
    (t.subst tf).realize v = t.realize fun a => (tf a).realize v :=
  by
  induction' t with _ _ _ _ ih
  · rfl
  · simp [ih]
#align first_order.language.term.realize_subst FirstOrder.Language.Term.realize_subst

@[simp]
theorem realize_restrictVar [DecidableEq α] {t : L.term α} {s : Set α} (h : ↑t.varFinset ⊆ s)
    {v : α → M} : (t.restrictVar (Set.inclusion h)).realize (v ∘ coe) = t.realize v :=
  by
  induction' t with _ _ _ _ ih
  · rfl
  · simp_rw [var_finset, Finset.coe_bunionᵢ, Set.unionᵢ_subset_iff] at h
    exact congr rfl (funext fun i => ih i (h i (Finset.mem_univ i)))
#align first_order.language.term.realize_restrict_var FirstOrder.Language.Term.realize_restrictVar

@[simp]
theorem realize_restrictVarLeft [DecidableEq α] {γ : Type _} {t : L.term (Sum α γ)} {s : Set α}
    (h : ↑t.varFinsetLeft ⊆ s) {v : α → M} {xs : γ → M} :
    (t.restrictVarLeft (Set.inclusion h)).realize (Sum.elim (v ∘ coe) xs) =
      t.realize (Sum.elim v xs) :=
  by
  induction' t with a _ _ _ ih
  · cases a <;> rfl
  · simp_rw [var_finset_left, Finset.coe_bunionᵢ, Set.unionᵢ_subset_iff] at h
    exact congr rfl (funext fun i => ih i (h i (Finset.mem_univ i)))
#align first_order.language.term.realize_restrict_var_left FirstOrder.Language.Term.realize_restrictVarLeft

@[simp]
theorem realize_constantsToVars [L[[α]].Structure M] [(lhomWithConstants L α).IsExpansionOn M]
    {t : L[[α]].term β} {v : β → M} :
    t.constantsToVars.realize (Sum.elim (fun a => ↑(L.con a)) v) = t.realize v :=
  by
  induction' t with _ n f _ ih
  · simp
  · cases n
    · cases f
      · simp [ih]
      · simp only [realize, constants_to_vars, Sum.elim_inl, fun_map_eq_coe_constants]
        rfl
    · cases f
      · simp [ih]
      · exact isEmptyElim f
#align first_order.language.term.realize_constants_to_vars FirstOrder.Language.Term.realize_constantsToVars

@[simp]
theorem realize_varsToConstants [L[[α]].Structure M] [(lhomWithConstants L α).IsExpansionOn M]
    {t : L.term (Sum α β)} {v : β → M} :
    t.varsToConstants.realize v = t.realize (Sum.elim (fun a => ↑(L.con a)) v) :=
  by
  induction' t with ab n f ts ih
  · cases ab <;> simp [language.con]
  · simp [ih]
#align first_order.language.term.realize_vars_to_constants FirstOrder.Language.Term.realize_varsToConstants

theorem realize_constantsVarsEquivLeft [L[[α]].Structure M]
    [(lhomWithConstants L α).IsExpansionOn M] {n} {t : L[[α]].term (Sum β (Fin n))} {v : β → M}
    {xs : Fin n → M} :
    (constantsVarsEquivLeft t).realize (Sum.elim (Sum.elim (fun a => ↑(L.con a)) v) xs) =
      t.realize (Sum.elim v xs) :=
  by
  simp only [constants_vars_equiv_left, realize_relabel, Equiv.coe_trans, Function.comp_apply,
    constants_vars_equiv_apply, relabel_equiv_symm_apply]
  refine' trans _ realize_constants_to_vars
  rcongr
  rcases x with (a | (b | i)) <;> simp
#align first_order.language.term.realize_constants_vars_equiv_left FirstOrder.Language.Term.realize_constantsVarsEquivLeft

end Term

namespace Lhom

@[simp]
theorem realize_onTerm [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M] (t : L.term α)
    (v : α → M) : (φ.onTerm t).realize v = t.realize v :=
  by
  induction' t with _ n f ts ih
  · rfl
  · simp only [term.realize, Lhom.on_term, Lhom.map_on_function, ih]
#align first_order.language.Lhom.realize_on_term FirstOrder.Language.Lhom.realize_onTerm

end Lhom

@[simp]
theorem Hom.realize_term (g : M →[L] N) {t : L.term α} {v : α → M} :
    t.realize (g ∘ v) = g (t.realize v) := by
  induction t
  · rfl
  · rw [term.realize, term.realize, g.map_fun]
    refine' congr rfl _
    ext x
    simp [t_ih x]
#align first_order.language.hom.realize_term FirstOrder.Language.Hom.realize_term

@[simp]
theorem Embedding.realize_term {v : α → M} (t : L.term α) (g : M ↪[L] N) :
    t.realize (g ∘ v) = g (t.realize v) :=
  g.toHom.realize_term
#align first_order.language.embedding.realize_term FirstOrder.Language.Embedding.realize_term

@[simp]
theorem Equiv.realize_term {v : α → M} (t : L.term α) (g : M ≃[L] N) :
    t.realize (g ∘ v) = g (t.realize v) :=
  g.toHom.realize_term
#align first_order.language.equiv.realize_term FirstOrder.Language.Equiv.realize_term

variable {L} {α} {n : ℕ}

namespace BoundedFormula

open Term

/- warning: first_order.language.bounded_formula.realize -> FirstOrder.Language.BoundedFormula.Realize is a dubious translation:
lean 3 declaration is
  forall {L : FirstOrder.Language.{u1, u2}} {M : Type.{u3}} [_inst_1 : FirstOrder.Language.Structure.{u1, u2, u3} L M] {α : Type.{u4}} {l : Nat}, (FirstOrder.Language.BoundedFormula.{u1, u2, u4} L α l) -> (α -> M) -> ((Fin l) -> M) -> Prop
but is expected to have type
  forall {L : FirstOrder.Language.{u1, u3}} {M : Type.{u4}} [_inst_1 : FirstOrder.Language.Structure.{u1, u3, u4} L M] {α : Type.{u2}} {l : Nat}, (FirstOrder.Language.BoundedFormula.{u1, u3, u2} L α l) -> (α -> M) -> ((Fin l) -> M) -> Prop
Case conversion may be inaccurate. Consider using '#align first_order.language.bounded_formula.realize FirstOrder.Language.BoundedFormula.Realizeₓ'. -/
/-- A bounded formula can be evaluated as true or false by giving values to each free variable. -/
def Realize : ∀ {l} (f : L.BoundedFormula α l) (v : α → M) (xs : Fin l → M), Prop
  | _, falsum, v, xs => False
  | _, bounded_formula.equal t₁ t₂, v, xs => t₁.realize (Sum.elim v xs) = t₂.realize (Sum.elim v xs)
  | _, bounded_formula.rel R ts, v, xs => RelMap R fun i => (ts i).realize (Sum.elim v xs)
  | _, bounded_formula.imp f₁ f₂, v, xs => realize f₁ v xs → realize f₂ v xs
  | _, bounded_formula.all f, v, xs => ∀ x : M, realize f v (snoc xs x)
#align first_order.language.bounded_formula.realize FirstOrder.Language.BoundedFormula.Realize

variable {l : ℕ} {φ ψ : L.BoundedFormula α l} {θ : L.BoundedFormula α l.succ}

variable {v : α → M} {xs : Fin l → M}

@[simp]
theorem realize_bot : (⊥ : L.BoundedFormula α l).realize v xs ↔ False :=
  Iff.rfl
#align first_order.language.bounded_formula.realize_bot FirstOrder.Language.BoundedFormula.realize_bot

@[simp]
theorem realize_not : φ.Not.realize v xs ↔ ¬φ.realize v xs :=
  Iff.rfl
#align first_order.language.bounded_formula.realize_not FirstOrder.Language.BoundedFormula.realize_not

@[simp]
theorem realize_bdEqual (t₁ t₂ : L.term (Sum α (Fin l))) :
    (t₁.bdEqual t₂).realize v xs ↔ t₁.realize (Sum.elim v xs) = t₂.realize (Sum.elim v xs) :=
  Iff.rfl
#align first_order.language.bounded_formula.realize_bd_equal FirstOrder.Language.BoundedFormula.realize_bdEqual

@[simp]
theorem realize_top : (⊤ : L.BoundedFormula α l).realize v xs ↔ True := by simp [Top.top]
#align first_order.language.bounded_formula.realize_top FirstOrder.Language.BoundedFormula.realize_top

@[simp]
theorem realize_inf : (φ ⊓ ψ).realize v xs ↔ φ.realize v xs ∧ ψ.realize v xs := by
  simp [Inf.inf, realize]
#align first_order.language.bounded_formula.realize_inf FirstOrder.Language.BoundedFormula.realize_inf

@[simp]
theorem realize_foldr_inf (l : List (L.BoundedFormula α n)) (v : α → M) (xs : Fin n → M) :
    (l.foldr (· ⊓ ·) ⊤).realize v xs ↔ ∀ φ ∈ l, BoundedFormula.Realize φ v xs :=
  by
  induction' l with φ l ih
  · simp
  · simp [ih]
#align first_order.language.bounded_formula.realize_foldr_inf FirstOrder.Language.BoundedFormula.realize_foldr_inf

@[simp]
theorem realize_imp : (φ.imp ψ).realize v xs ↔ φ.realize v xs → ψ.realize v xs := by
  simp only [realize]
#align first_order.language.bounded_formula.realize_imp FirstOrder.Language.BoundedFormula.realize_imp

@[simp]
theorem realize_rel {k : ℕ} {R : L.Relations k} {ts : Fin k → L.term _} :
    (R.BoundedFormula ts).realize v xs ↔ RelMap R fun i => (ts i).realize (Sum.elim v xs) :=
  Iff.rfl
#align first_order.language.bounded_formula.realize_rel FirstOrder.Language.BoundedFormula.realize_rel

@[simp]
theorem realize_rel₁ {R : L.Relations 1} {t : L.term _} :
    (R.boundedFormula₁ t).realize v xs ↔ RelMap R ![t.realize (Sum.elim v xs)] :=
  by
  rw [relations.bounded_formula₁, realize_rel, iff_eq_eq]
  refine' congr rfl (funext fun _ => _)
  simp only [Matrix.cons_val_fin_one]
#align first_order.language.bounded_formula.realize_rel₁ FirstOrder.Language.BoundedFormula.realize_rel₁

@[simp]
theorem realize_rel₂ {R : L.Relations 2} {t₁ t₂ : L.term _} :
    (R.boundedFormula₂ t₁ t₂).realize v xs ↔
      RelMap R ![t₁.realize (Sum.elim v xs), t₂.realize (Sum.elim v xs)] :=
  by
  rw [relations.bounded_formula₂, realize_rel, iff_eq_eq]
  refine' congr rfl (funext (Fin.cases _ _))
  · simp only [Matrix.cons_val_zero]
  · simp only [Matrix.cons_val_succ, Matrix.cons_val_fin_one, forall_const]
#align first_order.language.bounded_formula.realize_rel₂ FirstOrder.Language.BoundedFormula.realize_rel₂

@[simp]
theorem realize_sup : (φ ⊔ ψ).realize v xs ↔ φ.realize v xs ∨ ψ.realize v xs :=
  by
  simp only [realize, Sup.sup, realize_not, eq_iff_iff]
  tauto
#align first_order.language.bounded_formula.realize_sup FirstOrder.Language.BoundedFormula.realize_sup

@[simp]
theorem realize_foldr_sup (l : List (L.BoundedFormula α n)) (v : α → M) (xs : Fin n → M) :
    (l.foldr (· ⊔ ·) ⊥).realize v xs ↔ ∃ φ ∈ l, BoundedFormula.Realize φ v xs :=
  by
  induction' l with φ l ih
  · simp
  ·
    simp_rw [List.foldr_cons, realize_sup, ih, exists_prop, List.mem_cons, or_and_right, exists_or,
      exists_eq_left]
#align first_order.language.bounded_formula.realize_foldr_sup FirstOrder.Language.BoundedFormula.realize_foldr_sup

@[simp]
theorem realize_all : (all θ).realize v xs ↔ ∀ a : M, θ.realize v (Fin.snoc xs a) :=
  Iff.rfl
#align first_order.language.bounded_formula.realize_all FirstOrder.Language.BoundedFormula.realize_all

@[simp]
theorem realize_ex : θ.ex.realize v xs ↔ ∃ a : M, θ.realize v (Fin.snoc xs a) :=
  by
  rw [bounded_formula.ex, realize_not, realize_all, not_forall]
  simp_rw [realize_not, Classical.not_not]
#align first_order.language.bounded_formula.realize_ex FirstOrder.Language.BoundedFormula.realize_ex

@[simp]
theorem realize_iff : (φ.Iff ψ).realize v xs ↔ (φ.realize v xs ↔ ψ.realize v xs) := by
  simp only [bounded_formula.iff, realize_inf, realize_imp, and_imp, ← iff_def]
#align first_order.language.bounded_formula.realize_iff FirstOrder.Language.BoundedFormula.realize_iff

theorem realize_castLe_of_eq {m n : ℕ} (h : m = n) {h' : m ≤ n} {φ : L.BoundedFormula α m}
    {v : α → M} {xs : Fin n → M} : (φ.castLe h').realize v xs ↔ φ.realize v (xs ∘ Fin.cast h) :=
  by
  subst h
  simp only [cast_le_rfl, cast_refl, OrderIso.coe_refl, Function.comp.right_id]
#align first_order.language.bounded_formula.realize_cast_le_of_eq FirstOrder.Language.BoundedFormula.realize_castLe_of_eq

theorem realize_mapTermRel_id [L'.Structure M]
    {ft : ∀ n, L.term (Sum α (Fin n)) → L'.term (Sum β (Fin n))}
    {fr : ∀ n, L.Relations n → L'.Relations n} {n} {φ : L.BoundedFormula α n} {v : α → M}
    {v' : β → M} {xs : Fin n → M}
    (h1 :
      ∀ (n) (t : L.term (Sum α (Fin n))) (xs : Fin n → M),
        (ft n t).realize (Sum.elim v' xs) = t.realize (Sum.elim v xs))
    (h2 : ∀ (n) (R : L.Relations n) (x : Fin n → M), RelMap (fr n R) x = RelMap R x) :
    (φ.mapTermRel ft fr fun _ => id).realize v' xs ↔ φ.realize v xs :=
  by
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih
  · rfl
  · simp [map_term_rel, realize, h1]
  · simp [map_term_rel, realize, h1, h2]
  · simp [map_term_rel, realize, ih1, ih2]
  · simp only [map_term_rel, realize, ih, id.def]
#align first_order.language.bounded_formula.realize_map_term_rel_id FirstOrder.Language.BoundedFormula.realize_mapTermRel_id

theorem realize_mapTermRel_add_castLe [L'.Structure M] {k : ℕ}
    {ft : ∀ n, L.term (Sum α (Fin n)) → L'.term (Sum β (Fin (k + n)))}
    {fr : ∀ n, L.Relations n → L'.Relations n} {n} {φ : L.BoundedFormula α n}
    (v : ∀ {n}, (Fin (k + n) → M) → α → M) {v' : β → M} (xs : Fin (k + n) → M)
    (h1 :
      ∀ (n) (t : L.term (Sum α (Fin n))) (xs' : Fin (k + n) → M),
        (ft n t).realize (Sum.elim v' xs') = t.realize (Sum.elim (v xs') (xs' ∘ Fin.natAdd _)))
    (h2 : ∀ (n) (R : L.Relations n) (x : Fin n → M), RelMap (fr n R) x = RelMap R x)
    (hv : ∀ (n) (xs : Fin (k + n) → M) (x : M), @v (n + 1) (snoc xs x : Fin _ → M) = v xs) :
    (φ.mapTermRel ft fr fun n => castLe (add_assoc _ _ _).symm.le).realize v' xs ↔
      φ.realize (v xs) (xs ∘ Fin.natAdd _) :=
  by
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih
  · rfl
  · simp [map_term_rel, realize, h1]
  · simp [map_term_rel, realize, h1, h2]
  · simp [map_term_rel, realize, ih1, ih2]
  · simp [map_term_rel, realize, ih, hv]
#align first_order.language.bounded_formula.realize_map_term_rel_add_cast_le FirstOrder.Language.BoundedFormula.realize_mapTermRel_add_castLe

theorem realize_relabel {m n : ℕ} {φ : L.BoundedFormula α n} {g : α → Sum β (Fin m)} {v : β → M}
    {xs : Fin (m + n) → M} :
    (φ.relabel g).realize v xs ↔
      φ.realize (Sum.elim v (xs ∘ Fin.castAdd n) ∘ g) (xs ∘ Fin.natAdd m) :=
  by rw [relabel, realize_map_term_rel_add_cast_le] <;> intros <;> simp
#align first_order.language.bounded_formula.realize_relabel FirstOrder.Language.BoundedFormula.realize_relabel

theorem realize_liftAt {n n' m : ℕ} {φ : L.BoundedFormula α n} {v : α → M} {xs : Fin (n + n') → M}
    (hmn : m + n' ≤ n + 1) :
    (φ.liftAt n' m).realize v xs ↔
      φ.realize v (xs ∘ fun i => if ↑i < m then Fin.castAdd n' i else Fin.addNat n' i) :=
  by
  rw [lift_at]
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 k _ ih3
  · simp [realize, map_term_rel]
  · simp [realize, map_term_rel, realize_rel, realize_lift_at, Sum.elim_comp_map]
  · simp [realize, map_term_rel, realize_rel, realize_lift_at, Sum.elim_comp_map]
  · simp only [map_term_rel, realize, ih1 hmn, ih2 hmn]
  · have h : k + 1 + n' = k + n' + 1 := by rw [add_assoc, add_comm 1 n', ← add_assoc]
    simp only [map_term_rel, realize, realize_cast_le_of_eq h, ih3 (hmn.trans k.succ.le_succ)]
    refine' forall_congr' fun x => iff_eq_eq.mpr (congr rfl (funext (Fin.lastCases _ fun i => _)))
    · simp only [Function.comp_apply, coe_last, snoc_last]
      by_cases k < m
      · rw [if_pos h]
        refine' (congr rfl (ext _)).trans (snoc_last _ _)
        simp only [coe_cast, coe_cast_add, coe_last, self_eq_add_right]
        refine'
          le_antisymm (le_of_add_le_add_left ((hmn.trans (Nat.succ_le_of_lt h)).trans _)) n'.zero_le
        rw [add_zero]
      · rw [if_neg h]
        refine' (congr rfl (ext _)).trans (snoc_last _ _)
        simp
    · simp only [Function.comp_apply, Fin.snoc_cast_succ]
      refine' (congr rfl (ext _)).trans (snoc_cast_succ _ _ _)
      simp only [cast_refl, coe_cast_succ, OrderIso.coe_refl, id.def]
      split_ifs <;> simp
#align first_order.language.bounded_formula.realize_lift_at FirstOrder.Language.BoundedFormula.realize_liftAt

theorem realize_liftAt_one {n m : ℕ} {φ : L.BoundedFormula α n} {v : α → M} {xs : Fin (n + 1) → M}
    (hmn : m ≤ n) :
    (φ.liftAt 1 m).realize v xs ↔
      φ.realize v (xs ∘ fun i => if ↑i < m then castSucc i else i.succ) :=
  by simp_rw [realize_lift_at (add_le_add_right hmn 1), cast_succ, add_nat_one]
#align first_order.language.bounded_formula.realize_lift_at_one FirstOrder.Language.BoundedFormula.realize_liftAt_one

@[simp]
theorem realize_liftAt_one_self {n : ℕ} {φ : L.BoundedFormula α n} {v : α → M}
    {xs : Fin (n + 1) → M} : (φ.liftAt 1 n).realize v xs ↔ φ.realize v (xs ∘ castSucc) :=
  by
  rw [realize_lift_at_one (refl n), iff_eq_eq]
  refine' congr rfl (congr rfl (funext fun i => _))
  rw [if_pos i.is_lt]
#align first_order.language.bounded_formula.realize_lift_at_one_self FirstOrder.Language.BoundedFormula.realize_liftAt_one_self

theorem realize_subst {φ : L.BoundedFormula α n} {tf : α → L.term β} {v : β → M} {xs : Fin n → M} :
    (φ.subst tf).realize v xs ↔ φ.realize (fun a => (tf a).realize v) xs :=
  realize_mapTermRel_id
    (fun n t x => by
      rw [term.realize_subst]
      rcongr a
      · cases a
        · simp only [Sum.elim_inl, term.realize_relabel, Sum.elim_comp_inl]
        · rfl)
    (by simp)
#align first_order.language.bounded_formula.realize_subst FirstOrder.Language.BoundedFormula.realize_subst

@[simp]
theorem realize_restrictFreeVar [DecidableEq α] {n : ℕ} {φ : L.BoundedFormula α n} {s : Set α}
    (h : ↑φ.freeVarFinset ⊆ s) {v : α → M} {xs : Fin n → M} :
    (φ.restrictFreeVar (Set.inclusion h)).realize (v ∘ coe) xs ↔ φ.realize v xs :=
  by
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih3
  · rfl
  · simp [restrict_free_var, realize]
  · simp [restrict_free_var, realize]
  · simp [restrict_free_var, realize, ih1, ih2]
  · simp [restrict_free_var, realize, ih3]
#align first_order.language.bounded_formula.realize_restrict_free_var FirstOrder.Language.BoundedFormula.realize_restrictFreeVar

theorem realize_constantsVarsEquiv [L[[α]].Structure M] [(lhomWithConstants L α).IsExpansionOn M]
    {n} {φ : L[[α]].BoundedFormula β n} {v : β → M} {xs : Fin n → M} :
    (constantsVarsEquiv φ).realize (Sum.elim (fun a => ↑(L.con a)) v) xs ↔ φ.realize v xs :=
  by
  refine' realize_map_term_rel_id (fun n t xs => realize_constants_vars_equiv_left) fun n R xs => _
  rw [←
    (Lhom_with_constants L α).map_onRelation
      (Equiv.sumEmpty (L.relations n) ((constants_on α).Relations n) R) xs]
  rcongr
  cases R
  · simp
  · exact isEmptyElim R
#align first_order.language.bounded_formula.realize_constants_vars_equiv FirstOrder.Language.BoundedFormula.realize_constantsVarsEquiv

@[simp]
theorem realize_relabelEquiv {g : α ≃ β} {k} {φ : L.BoundedFormula α k} {v : β → M}
    {xs : Fin k → M} : (relabelEquiv g φ).realize v xs ↔ φ.realize (v ∘ g) xs :=
  by
  simp only [relabel_equiv, map_term_rel_equiv_apply, Equiv.coe_refl]
  refine' realize_map_term_rel_id (fun n t xs => _) fun _ _ _ => rfl
  simp only [relabel_equiv_apply, term.realize_relabel]
  refine' congr (congr rfl _) rfl
  ext (i | i) <;> rfl
#align first_order.language.bounded_formula.realize_relabel_equiv FirstOrder.Language.BoundedFormula.realize_relabelEquiv

variable [Nonempty M]

theorem realize_all_liftAt_one_self {n : ℕ} {φ : L.BoundedFormula α n} {v : α → M}
    {xs : Fin n → M} : (φ.liftAt 1 n).all.realize v xs ↔ φ.realize v xs :=
  by
  inhabit M
  simp only [realize_all, realize_lift_at_one_self]
  refine' ⟨fun h => _, fun h a => _⟩
  · refine' (congr rfl (funext fun i => _)).mp (h default)
    simp
  · refine' (congr rfl (funext fun i => _)).mp h
    simp
#align first_order.language.bounded_formula.realize_all_lift_at_one_self FirstOrder.Language.BoundedFormula.realize_all_liftAt_one_self

theorem realize_toPrenexImpRight {φ ψ : L.BoundedFormula α n} (hφ : IsQf φ) (hψ : IsPrenex ψ)
    {v : α → M} {xs : Fin n → M} : (φ.toPrenexImpRight ψ).realize v xs ↔ (φ.imp ψ).realize v xs :=
  by
  revert φ
  induction' hψ with _ _ hψ _ _ hψ ih _ _ hψ ih <;> intro φ hφ
  · rw [hψ.to_prenex_imp_right]
  · refine' trans (forall_congr' fun _ => ih hφ.lift_at) _
    simp only [realize_imp, realize_lift_at_one_self, snoc_comp_cast_succ, realize_all]
    exact ⟨fun h1 a h2 => h1 h2 a, fun h1 h2 a => h1 a h2⟩
  · rw [to_prenex_imp_right, realize_ex]
    refine' trans (exists_congr fun _ => ih hφ.lift_at) _
    simp only [realize_imp, realize_lift_at_one_self, snoc_comp_cast_succ, realize_ex]
    refine' ⟨_, fun h' => _⟩
    · rintro ⟨a, ha⟩ h
      exact ⟨a, ha h⟩
    · by_cases φ.realize v xs
      · obtain ⟨a, ha⟩ := h' h
        exact ⟨a, fun _ => ha⟩
      · inhabit M
        exact ⟨default, fun h'' => (h h'').elim⟩
#align first_order.language.bounded_formula.realize_to_prenex_imp_right FirstOrder.Language.BoundedFormula.realize_toPrenexImpRight

theorem realize_toPrenexImp {φ ψ : L.BoundedFormula α n} (hφ : IsPrenex φ) (hψ : IsPrenex ψ)
    {v : α → M} {xs : Fin n → M} : (φ.toPrenexImp ψ).realize v xs ↔ (φ.imp ψ).realize v xs :=
  by
  revert ψ
  induction' hφ with _ _ hφ _ _ hφ ih _ _ hφ ih <;> intro ψ hψ
  · rw [hφ.to_prenex_imp]
    exact realize_to_prenex_imp_right hφ hψ
  · rw [to_prenex_imp, realize_ex]
    refine' trans (exists_congr fun _ => ih hψ.lift_at) _
    simp only [realize_imp, realize_lift_at_one_self, snoc_comp_cast_succ, realize_all]
    refine' ⟨_, fun h' => _⟩
    · rintro ⟨a, ha⟩ h
      exact ha (h a)
    · by_cases ψ.realize v xs
      · inhabit M
        exact ⟨default, fun h'' => h⟩
      · obtain ⟨a, ha⟩ := not_forall.1 (h ∘ h')
        exact ⟨a, fun h => (ha h).elim⟩
  · refine' trans (forall_congr' fun _ => ih hψ.lift_at) _
    simp
#align first_order.language.bounded_formula.realize_to_prenex_imp FirstOrder.Language.BoundedFormula.realize_toPrenexImp

@[simp]
theorem realize_toPrenex (φ : L.BoundedFormula α n) {v : α → M} :
    ∀ {xs : Fin n → M}, φ.toPrenex.realize v xs ↔ φ.realize v xs :=
  by
  refine'
    bounded_formula.rec_on φ (fun _ _ => Iff.rfl) (fun _ _ _ _ => Iff.rfl)
      (fun _ _ _ _ _ => Iff.rfl) (fun _ f1 f2 h1 h2 _ => _) fun _ f h xs => _
  · rw [to_prenex, realize_to_prenex_imp f1.to_prenex_is_prenex f2.to_prenex_is_prenex, realize_imp,
      realize_imp, h1, h2]
    infer_instance
  · rw [realize_all, to_prenex, realize_all]
    exact forall_congr' fun a => h
#align first_order.language.bounded_formula.realize_to_prenex FirstOrder.Language.BoundedFormula.realize_toPrenex

end BoundedFormula

attribute [protected] bounded_formula.falsum bounded_formula.equal bounded_formula.rel

attribute [protected] bounded_formula.imp bounded_formula.all

namespace Lhom

open BoundedFormula

@[simp]
theorem realize_onBoundedFormula [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M] {n : ℕ}
    (ψ : L.BoundedFormula α n) {v : α → M} {xs : Fin n → M} :
    (φ.onBoundedFormula ψ).realize v xs ↔ ψ.realize v xs :=
  by
  induction' ψ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih3
  · rfl
  · simp only [on_bounded_formula, realize_bd_equal, realize_on_term]
    rfl
  · simp only [on_bounded_formula, realize_rel, realize_on_term, Lhom.map_on_relation]
    rfl
  · simp only [on_bounded_formula, ih1, ih2, realize_imp]
  · simp only [on_bounded_formula, ih3, realize_all]
#align first_order.language.Lhom.realize_on_bounded_formula FirstOrder.Language.Lhom.realize_onBoundedFormula

end Lhom

attribute [protected] bounded_formula.falsum bounded_formula.equal bounded_formula.rel

attribute [protected] bounded_formula.imp bounded_formula.all

namespace Formula

/-- A formula can be evaluated as true or false by giving values to each free variable. -/
def Realize (φ : L.Formula α) (v : α → M) : Prop :=
  φ.realize v default
#align first_order.language.formula.realize FirstOrder.Language.Formula.Realize

variable {M} {φ ψ : L.Formula α} {v : α → M}

@[simp]
theorem realize_not : φ.Not.realize v ↔ ¬φ.realize v :=
  Iff.rfl
#align first_order.language.formula.realize_not FirstOrder.Language.Formula.realize_not

@[simp]
theorem realize_bot : (⊥ : L.Formula α).realize v ↔ False :=
  Iff.rfl
#align first_order.language.formula.realize_bot FirstOrder.Language.Formula.realize_bot

@[simp]
theorem realize_top : (⊤ : L.Formula α).realize v ↔ True :=
  BoundedFormula.realize_top
#align first_order.language.formula.realize_top FirstOrder.Language.Formula.realize_top

@[simp]
theorem realize_inf : (φ ⊓ ψ).realize v ↔ φ.realize v ∧ ψ.realize v :=
  BoundedFormula.realize_inf
#align first_order.language.formula.realize_inf FirstOrder.Language.Formula.realize_inf

@[simp]
theorem realize_imp : (φ.imp ψ).realize v ↔ φ.realize v → ψ.realize v :=
  BoundedFormula.realize_imp
#align first_order.language.formula.realize_imp FirstOrder.Language.Formula.realize_imp

@[simp]
theorem realize_rel {k : ℕ} {R : L.Relations k} {ts : Fin k → L.term α} :
    (R.Formula ts).realize v ↔ RelMap R fun i => (ts i).realize v :=
  BoundedFormula.realize_rel.trans (by simp)
#align first_order.language.formula.realize_rel FirstOrder.Language.Formula.realize_rel

@[simp]
theorem realize_rel₁ {R : L.Relations 1} {t : L.term _} :
    (R.formula₁ t).realize v ↔ RelMap R ![t.realize v] :=
  by
  rw [relations.formula₁, realize_rel, iff_eq_eq]
  refine' congr rfl (funext fun _ => _)
  simp only [Matrix.cons_val_fin_one]
#align first_order.language.formula.realize_rel₁ FirstOrder.Language.Formula.realize_rel₁

@[simp]
theorem realize_rel₂ {R : L.Relations 2} {t₁ t₂ : L.term _} :
    (R.formula₂ t₁ t₂).realize v ↔ RelMap R ![t₁.realize v, t₂.realize v] :=
  by
  rw [relations.formula₂, realize_rel, iff_eq_eq]
  refine' congr rfl (funext (Fin.cases _ _))
  · simp only [Matrix.cons_val_zero]
  · simp only [Matrix.cons_val_succ, Matrix.cons_val_fin_one, forall_const]
#align first_order.language.formula.realize_rel₂ FirstOrder.Language.Formula.realize_rel₂

@[simp]
theorem realize_sup : (φ ⊔ ψ).realize v ↔ φ.realize v ∨ ψ.realize v :=
  BoundedFormula.realize_sup
#align first_order.language.formula.realize_sup FirstOrder.Language.Formula.realize_sup

@[simp]
theorem realize_iff : (φ.Iff ψ).realize v ↔ (φ.realize v ↔ ψ.realize v) :=
  BoundedFormula.realize_iff
#align first_order.language.formula.realize_iff FirstOrder.Language.Formula.realize_iff

@[simp]
theorem realize_relabel {φ : L.Formula α} {g : α → β} {v : β → M} :
    (φ.relabel g).realize v ↔ φ.realize (v ∘ g) :=
  by
  rw [realize, realize, relabel, bounded_formula.realize_relabel, iff_eq_eq, Fin.castAdd_zero]
  exact congr rfl (funext finZeroElim)
#align first_order.language.formula.realize_relabel FirstOrder.Language.Formula.realize_relabel

theorem realize_relabel_sum_inr (φ : L.Formula (Fin n)) {v : Empty → M} {x : Fin n → M} :
    (BoundedFormula.relabel Sum.inr φ).realize v x ↔ φ.realize x := by
  rw [bounded_formula.realize_relabel, formula.realize, Sum.elim_comp_inr, Fin.castAdd_zero,
    cast_refl, OrderIso.coe_refl, Function.comp.right_id,
    Subsingleton.elim (x ∘ (nat_add n : Fin 0 → Fin n)) default]
#align first_order.language.formula.realize_relabel_sum_inr FirstOrder.Language.Formula.realize_relabel_sum_inr

@[simp]
theorem realize_equal {t₁ t₂ : L.term α} {x : α → M} :
    (t₁.equal t₂).realize x ↔ t₁.realize x = t₂.realize x := by simp [term.equal, realize]
#align first_order.language.formula.realize_equal FirstOrder.Language.Formula.realize_equal

@[simp]
theorem realize_graph {f : L.Functions n} {x : Fin n → M} {y : M} :
    (Formula.graph f).realize (Fin.cons y x : _ → M) ↔ funMap f x = y :=
  by
  simp only [formula.graph, term.realize, realize_equal, Fin.cons_zero, Fin.cons_succ]
  rw [eq_comm]
#align first_order.language.formula.realize_graph FirstOrder.Language.Formula.realize_graph

end Formula

@[simp]
theorem Lhom.realize_onFormula [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M] (ψ : L.Formula α)
    {v : α → M} : (φ.onFormula ψ).realize v ↔ ψ.realize v :=
  φ.realize_onBoundedFormula ψ
#align first_order.language.Lhom.realize_on_formula FirstOrder.Language.Lhom.realize_onFormula

@[simp]
theorem Lhom.setOf_realize_onFormula [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M]
    (ψ : L.Formula α) : (setOf (φ.onFormula ψ).realize : Set (α → M)) = setOf ψ.realize :=
  by
  ext
  simp
#align first_order.language.Lhom.set_of_realize_on_formula FirstOrder.Language.Lhom.setOf_realize_onFormula

variable (M)

/-- A sentence can be evaluated as true or false in a structure. -/
def Sentence.Realize (φ : L.Sentence) : Prop :=
  φ.realize (default : _ → M)
#align first_order.language.sentence.realize FirstOrder.Language.Sentence.Realize

-- mathport name: sentence.realize
infixl:51
  " ⊨ " =>-- input using \|= or \vDash, but not using \models
  Sentence.Realize

@[simp]
theorem Sentence.realize_not {φ : L.Sentence} : M ⊨ φ.Not ↔ ¬M ⊨ φ :=
  Iff.rfl
#align first_order.language.sentence.realize_not FirstOrder.Language.Sentence.realize_not

namespace Formula

@[simp]
theorem realize_equivSentence_symm_con [L[[α]].Structure M]
    [(L.lhomWithConstants α).IsExpansionOn M] (φ : L[[α]].Sentence) :
    ((equivSentence.symm φ).realize fun a => (L.con a : M)) ↔ φ.realize M :=
  by
  simp only [equiv_sentence, Equiv.symm_symm, Equiv.coe_trans, realize,
    bounded_formula.realize_relabel_equiv]
  refine' trans _ bounded_formula.realize_constants_vars_equiv
  congr with (i | i)
  · rfl
  · exact i.elim
#align first_order.language.formula.realize_equiv_sentence_symm_con FirstOrder.Language.Formula.realize_equivSentence_symm_con

@[simp]
theorem realize_equivSentence [L[[α]].Structure M] [(L.lhomWithConstants α).IsExpansionOn M]
    (φ : L.Formula α) : (equivSentence φ).realize M ↔ φ.realize fun a => (L.con a : M) := by
  rw [← realize_equiv_sentence_symm_con M (equiv_sentence φ), _root_.equiv.symm_apply_apply]
#align first_order.language.formula.realize_equiv_sentence FirstOrder.Language.Formula.realize_equivSentence

theorem realize_equivSentence_symm (φ : L[[α]].Sentence) (v : α → M) :
    (equivSentence.symm φ).realize v ↔
      @Sentence.Realize _ M (@Language.withConstantsStructure L M _ α (constantsOn.structure v))
        φ :=
  letI := constants_on.Structure v
  realize_equiv_sentence_symm_con M φ
#align first_order.language.formula.realize_equiv_sentence_symm FirstOrder.Language.Formula.realize_equivSentence_symm

end Formula

@[simp]
theorem Lhom.realize_onSentence [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M]
    (ψ : L.Sentence) : M ⊨ φ.onSentence ψ ↔ M ⊨ ψ :=
  φ.realize_onFormula ψ
#align first_order.language.Lhom.realize_on_sentence FirstOrder.Language.Lhom.realize_onSentence

variable (L)

/-- The complete theory of a structure `M` is the set of all sentences `M` satisfies. -/
def completeTheory : L.Theory :=
  { φ | M ⊨ φ }
#align first_order.language.complete_theory FirstOrder.Language.completeTheory

variable (N)

/-- Two structures are elementarily equivalent when they satisfy the same sentences. -/
def ElementarilyEquivalent : Prop :=
  L.completeTheory M = L.completeTheory N
#align first_order.language.elementarily_equivalent FirstOrder.Language.ElementarilyEquivalent

-- mathport name: elementarily_equivalent
scoped[FirstOrder]
  notation:25 A " ≅[" L "] " B:50 => FirstOrder.Language.ElementarilyEquivalent L A B

variable {L} {M} {N}

@[simp]
theorem mem_completeTheory {φ : Sentence L} : φ ∈ L.completeTheory M ↔ M ⊨ φ :=
  Iff.rfl
#align first_order.language.mem_complete_theory FirstOrder.Language.mem_completeTheory

theorem elementarilyEquivalent_iff : M ≅[L] N ↔ ∀ φ : L.Sentence, M ⊨ φ ↔ N ⊨ φ := by
  simp only [elementarily_equivalent, Set.ext_iff, complete_theory, Set.mem_setOf_eq]
#align first_order.language.elementarily_equivalent_iff FirstOrder.Language.elementarilyEquivalent_iff

variable (M)

/-- A model of a theory is a structure in which every sentence is realized as true. -/
class Theory.Model (T : L.Theory) : Prop where
  realize_of_mem : ∀ φ ∈ T, M ⊨ φ
#align first_order.language.Theory.model FirstOrder.Language.Theory.Model

-- mathport name: Theory.model
infixl:51
  " ⊨ " =>-- input using \|= or \vDash, but not using \models
  Theory.Model

variable {M} (T : L.Theory)

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem Theory.model_iff : M ⊨ T ↔ ∀ φ ∈ T, M ⊨ φ :=
  ⟨fun h => h.realize_of_mem, fun h => ⟨h⟩⟩
#align first_order.language.Theory.model_iff FirstOrder.Language.Theory.model_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem Theory.realize_sentence_of_mem [M ⊨ T] {φ : L.Sentence} (h : φ ∈ T) : M ⊨ φ :=
  Theory.Model.realize_of_mem φ h
#align first_order.language.Theory.realize_sentence_of_mem FirstOrder.Language.Theory.realize_sentence_of_mem

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem Lhom.onTheory_model [L'.Structure M] (φ : L →ᴸ L') [φ.IsExpansionOn M] (T : L.Theory) :
    M ⊨ φ.onTheory T ↔ M ⊨ T := by simp [Theory.model_iff, Lhom.on_Theory]
#align first_order.language.Lhom.on_Theory_model FirstOrder.Language.Lhom.onTheory_model

variable {M} {T}

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
instance model_empty : M ⊨ (∅ : L.Theory) :=
  ⟨fun φ hφ => (Set.not_mem_empty φ hφ).elim⟩
#align first_order.language.model_empty FirstOrder.Language.model_empty

namespace Theory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem Model.mono {T' : L.Theory} (h : M ⊨ T') (hs : T ⊆ T') : M ⊨ T :=
  ⟨fun φ hφ => T'.realize_sentence_of_mem (hs hφ)⟩
#align first_order.language.Theory.model.mono FirstOrder.Language.Theory.Model.mono

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem Model.union {T' : L.Theory} (h : M ⊨ T) (h' : M ⊨ T') : M ⊨ T ∪ T' :=
  by
  simp only [model_iff, Set.mem_union] at *
  exact fun φ hφ => hφ.elim (h _) (h' _)
#align first_order.language.Theory.model.union FirstOrder.Language.Theory.Model.union

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem model_union_iff {T' : L.Theory} : M ⊨ T ∪ T' ↔ M ⊨ T ∧ M ⊨ T' :=
  ⟨fun h => ⟨h.mono (T.subset_union_left T'), h.mono (T.subset_union_right T')⟩, fun h =>
    h.1.union h.2⟩
#align first_order.language.Theory.model_union_iff FirstOrder.Language.Theory.model_union_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem model_singleton_iff {φ : L.Sentence} : M ⊨ ({φ} : L.Theory) ↔ M ⊨ φ := by simp
#align first_order.language.Theory.model_singleton_iff FirstOrder.Language.Theory.model_singleton_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem model_iff_subset_completeTheory : M ⊨ T ↔ T ⊆ L.completeTheory M :=
  T.model_iff
#align first_order.language.Theory.model_iff_subset_complete_theory FirstOrder.Language.Theory.model_iff_subset_completeTheory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem CompleteTheory.subset [MT : M ⊨ T] : T ⊆ L.completeTheory M :=
  model_iff_subset_completeTheory.1 MT
#align first_order.language.Theory.complete_theory.subset FirstOrder.Language.Theory.CompleteTheory.subset

end Theory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
instance model_completeTheory : M ⊨ L.completeTheory M :=
  Theory.model_iff_subset_completeTheory.2 (subset_refl _)
#align first_order.language.model_complete_theory FirstOrder.Language.model_completeTheory

variable (M N)

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem realize_iff_of_model_completeTheory [N ⊨ L.completeTheory M] (φ : L.Sentence) :
    N ⊨ φ ↔ M ⊨ φ :=
  by
  refine' ⟨fun h => _, (L.complete_theory M).realize_sentence_of_mem⟩
  contrapose! h
  rw [← sentence.realize_not] at *
  exact (L.complete_theory M).realize_sentence_of_mem (mem_complete_theory.2 h)
#align first_order.language.realize_iff_of_model_complete_theory FirstOrder.Language.realize_iff_of_model_completeTheory

variable {M N}

namespace BoundedFormula

@[simp]
theorem realize_alls {φ : L.BoundedFormula α n} {v : α → M} :
    φ.alls.realize v ↔ ∀ xs : Fin n → M, φ.realize v xs :=
  by
  induction' n with n ih
  · exact unique.forall_iff.symm
  · simp only [alls, ih, realize]
    exact ⟨fun h xs => Fin.snoc_init_self xs ▸ h _ _, fun h xs x => h (Fin.snoc xs x)⟩
#align first_order.language.bounded_formula.realize_alls FirstOrder.Language.BoundedFormula.realize_alls

@[simp]
theorem realize_exs {φ : L.BoundedFormula α n} {v : α → M} :
    φ.exs.realize v ↔ ∃ xs : Fin n → M, φ.realize v xs :=
  by
  induction' n with n ih
  · exact unique.exists_iff.symm
  · simp only [bounded_formula.exs, ih, realize_ex]
    constructor
    · rintro ⟨xs, x, h⟩
      exact ⟨_, h⟩
    · rintro ⟨xs, h⟩
      rw [← Fin.snoc_init_self xs] at h
      exact ⟨_, _, h⟩
#align first_order.language.bounded_formula.realize_exs FirstOrder.Language.BoundedFormula.realize_exs

@[simp]
theorem realize_toFormula (φ : L.BoundedFormula α n) (v : Sum α (Fin n) → M) :
    φ.toFormula.realize v ↔ φ.realize (v ∘ Sum.inl) (v ∘ Sum.inr) :=
  by
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih3 a8 a9 a0
  · rfl
  · simp [bounded_formula.realize]
  · simp [bounded_formula.realize]
  ·
    rw [to_formula, formula.realize, realize_imp, ← formula.realize, ih1, ← formula.realize, ih2,
      realize_imp]
  · rw [to_formula, formula.realize, realize_all, realize_all]
    refine' forall_congr' fun a => _
    have h := ih3 (Sum.elim (v ∘ Sum.inl) (snoc (v ∘ Sum.inr) a))
    simp only [Sum.elim_comp_inl, Sum.elim_comp_inr] at h
    rw [← h, realize_relabel, formula.realize]
    rcongr
    · cases x
      · simp
      · refine' Fin.lastCases _ (fun i => _) x
        · rw [Sum.elim_inr, snoc_last, Function.comp_apply, Sum.elim_inr, Function.comp_apply,
            finSumFinEquiv_symm_last, Sum.map_inr, Sum.elim_inr, Function.comp_apply]
          exact (congr rfl (Subsingleton.elim _ _)).trans (snoc_last _ _)
        · simp only [cast_succ, Function.comp_apply, Sum.elim_inr,
            finSumFinEquiv_symm_apply_castAdd, Sum.map_inl, Sum.elim_inl]
          rw [← cast_succ, snoc_cast_succ]
    · exact Subsingleton.elim _ _
#align first_order.language.bounded_formula.realize_to_formula FirstOrder.Language.BoundedFormula.realize_toFormula

end BoundedFormula

namespace Equiv

@[simp]
theorem realize_boundedFormula (g : M ≃[L] N) (φ : L.BoundedFormula α n) {v : α → M}
    {xs : Fin n → M} : φ.realize (g ∘ v) (g ∘ xs) ↔ φ.realize v xs :=
  by
  induction' φ with _ _ _ _ _ _ _ _ _ _ _ ih1 ih2 _ _ ih3
  · rfl
  · simp only [bounded_formula.realize, ← Sum.comp_elim, equiv.realize_term, g.injective.eq_iff]
  · simp only [bounded_formula.realize, ← Sum.comp_elim, equiv.realize_term, g.map_rel]
  · rw [bounded_formula.realize, ih1, ih2, bounded_formula.realize]
  · rw [bounded_formula.realize, bounded_formula.realize]
    constructor
    · intro h a
      have h' := h (g a)
      rw [← Fin.comp_snoc, ih3] at h'
      exact h'
    · intro h a
      have h' := h (g.symm a)
      rw [← ih3, Fin.comp_snoc, g.apply_symm_apply] at h'
      exact h'
#align first_order.language.equiv.realize_bounded_formula FirstOrder.Language.Equiv.realize_boundedFormula

@[simp]
theorem realize_formula (g : M ≃[L] N) (φ : L.Formula α) {v : α → M} :
    φ.realize (g ∘ v) ↔ φ.realize v := by
  rw [formula.realize, formula.realize, ← g.realize_bounded_formula φ, iff_eq_eq,
    Unique.eq_default (g ∘ default)]
#align first_order.language.equiv.realize_formula FirstOrder.Language.Equiv.realize_formula

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem realize_sentence (g : M ≃[L] N) (φ : L.Sentence) : M ⊨ φ ↔ N ⊨ φ := by
  rw [sentence.realize, sentence.realize, ← g.realize_formula, Unique.eq_default (g ∘ default)]
#align first_order.language.equiv.realize_sentence FirstOrder.Language.Equiv.realize_sentence

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem theory_model (g : M ≃[L] N) [M ⊨ T] : N ⊨ T :=
  ⟨fun φ hφ => (g.realize_sentence φ).1 (Theory.realize_sentence_of_mem T hφ)⟩
#align first_order.language.equiv.Theory_model FirstOrder.Language.Equiv.theory_model

theorem elementarilyEquivalent (g : M ≃[L] N) : M ≅[L] N :=
  elementarilyEquivalent_iff.2 g.realize_sentence
#align first_order.language.equiv.elementarily_equivalent FirstOrder.Language.Equiv.elementarilyEquivalent

end Equiv

namespace Relations

open BoundedFormula

variable {r : L.Relations 2}

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_reflexive : M ⊨ r.Reflexive ↔ Reflexive fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ => realize_rel₂
#align first_order.language.relations.realize_reflexive FirstOrder.Language.Relations.realize_reflexive

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_irreflexive : M ⊨ r.Irreflexive ↔ Irreflexive fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ => not_congr realize_rel₂
#align first_order.language.relations.realize_irreflexive FirstOrder.Language.Relations.realize_irreflexive

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_symmetric : M ⊨ r.Symmetric ↔ Symmetric fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ => forall_congr' fun _ => imp_congr realize_rel₂ realize_rel₂
#align first_order.language.relations.realize_symmetric FirstOrder.Language.Relations.realize_symmetric

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_antisymmetric :
    M ⊨ r.antisymmetric ↔ AntiSymmetric fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ =>
    forall_congr' fun _ => imp_congr realize_rel₂ (imp_congr realize_rel₂ Iff.rfl)
#align first_order.language.relations.realize_antisymmetric FirstOrder.Language.Relations.realize_antisymmetric

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_transitive : M ⊨ r.Transitive ↔ Transitive fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ =>
    forall_congr' fun _ =>
      forall_congr' fun _ => imp_congr realize_rel₂ (imp_congr realize_rel₂ realize_rel₂)
#align first_order.language.relations.realize_transitive FirstOrder.Language.Relations.realize_transitive

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem realize_total : M ⊨ r.Total ↔ Total fun x y : M => RelMap r ![x, y] :=
  forall_congr' fun _ =>
    forall_congr' fun _ => realize_sup.trans (or_congr realize_rel₂ realize_rel₂)
#align first_order.language.relations.realize_total FirstOrder.Language.Relations.realize_total

end Relations

section Cardinality

variable (L)

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem Sentence.realize_cardGe (n) : M ⊨ Sentence.cardGe L n ↔ ↑n ≤ (#M) :=
  by
  rw [← lift_mk_fin, ← lift_le, lift_lift, lift_mk_le, sentence.card_ge, sentence.realize,
    bounded_formula.realize_exs]
  simp_rw [bounded_formula.realize_foldr_inf]
  simp only [Function.comp_apply, List.mem_map, Prod.exists, Ne.def, List.mem_product,
    List.mem_finRange, forall_exists_index, and_imp, List.mem_filter, true_and_iff]
  refine' ⟨_, fun xs => ⟨xs.some, _⟩⟩
  · rintro ⟨xs, h⟩
    refine' ⟨⟨xs, fun i j ij => _⟩⟩
    contrapose! ij
    have hij := h _ i j ij rfl
    simp only [bounded_formula.realize_not, term.realize, bounded_formula.realize_bd_equal,
      Sum.elim_inr] at hij
    exact hij
  · rintro _ i j ij rfl
    simp [ij]
#align first_order.language.sentence.realize_card_ge FirstOrder.Language.Sentence.realize_cardGe

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem model_infiniteTheory_iff : M ⊨ L.infiniteTheory ↔ Infinite M := by
  simp [infinite_theory, infinite_iff, aleph_0_le]
#align first_order.language.model_infinite_theory_iff FirstOrder.Language.model_infiniteTheory_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
instance model_infiniteTheory [h : Infinite M] : M ⊨ L.infiniteTheory :=
  L.model_infiniteTheory_iff.2 h
#align first_order.language.model_infinite_theory FirstOrder.Language.model_infiniteTheory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem model_nonemptyTheory_iff : M ⊨ L.nonemptyTheory ↔ Nonempty M := by
  simp only [nonempty_theory, Theory.model_iff, Set.mem_singleton_iff, forall_eq,
    sentence.realize_card_ge, Nat.cast_one, one_le_iff_ne_zero, mk_ne_zero_iff]
#align first_order.language.model_nonempty_theory_iff FirstOrder.Language.model_nonemptyTheory_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
instance model_nonempty [h : Nonempty M] : M ⊨ L.nonemptyTheory :=
  L.model_nonemptyTheory_iff.2 h
#align first_order.language.model_nonempty FirstOrder.Language.model_nonempty

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem model_distinctConstantsTheory {M : Type w} [L[[α]].Structure M] (s : Set α) :
    M ⊨ L.distinctConstantsTheory s ↔ Set.InjOn (fun i : α => (L.con i : M)) s :=
  by
  simp only [distinct_constants_theory, Theory.model_iff, Set.mem_image, Set.mem_inter,
    Set.mem_prod, Set.mem_compl, Prod.exists, forall_exists_index, and_imp]
  refine' ⟨fun h a as b bs ab => _, _⟩
  · contrapose! ab
    have h' := h _ a b ⟨⟨as, bs⟩, ab⟩ rfl
    simp only [sentence.realize, formula.realize_not, formula.realize_equal,
      term.realize_constants] at h'
    exact h'
  · rintro h φ a b ⟨⟨as, bs⟩, ab⟩ rfl
    simp only [sentence.realize, formula.realize_not, formula.realize_equal, term.realize_constants]
    exact fun contra => ab (h as bs contra)
#align first_order.language.model_distinct_constants_theory FirstOrder.Language.model_distinctConstantsTheory

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem card_le_of_model_distinctConstantsTheory (s : Set α) (M : Type w) [L[[α]].Structure M]
    [h : M ⊨ L.distinctConstantsTheory s] : Cardinal.lift.{w} (#s) ≤ Cardinal.lift.{u'} (#M) :=
  lift_mk_le'.2 ⟨⟨_, Set.injOn_iff_injective.1 ((L.model_distinctConstantsTheory s).1 h)⟩⟩
#align first_order.language.card_le_of_model_distinct_constants_theory FirstOrder.Language.card_le_of_model_distinctConstantsTheory

end Cardinality

namespace ElementarilyEquivalent

@[symm]
theorem symm (h : M ≅[L] N) : N ≅[L] M :=
  h.symm
#align first_order.language.elementarily_equivalent.symm FirstOrder.Language.ElementarilyEquivalent.symm

@[trans]
theorem trans (MN : M ≅[L] N) (NP : N ≅[L] P) : M ≅[L] P :=
  MN.trans NP
#align first_order.language.elementarily_equivalent.trans FirstOrder.Language.ElementarilyEquivalent.trans

theorem completeTheory_eq (h : M ≅[L] N) : L.completeTheory M = L.completeTheory N :=
  h
#align first_order.language.elementarily_equivalent.complete_theory_eq FirstOrder.Language.ElementarilyEquivalent.completeTheory_eq

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem realize_sentence (h : M ≅[L] N) (φ : L.Sentence) : M ⊨ φ ↔ N ⊨ φ :=
  (elementarilyEquivalent_iff.1 h) φ
#align first_order.language.elementarily_equivalent.realize_sentence FirstOrder.Language.ElementarilyEquivalent.realize_sentence

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem theory_model_iff (h : M ≅[L] N) : M ⊨ T ↔ N ⊨ T := by
  rw [Theory.model_iff_subset_complete_theory, Theory.model_iff_subset_complete_theory,
    h.complete_theory_eq]
#align first_order.language.elementarily_equivalent.Theory_model_iff FirstOrder.Language.ElementarilyEquivalent.theory_model_iff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem theory_model [MT : M ⊨ T] (h : M ≅[L] N) : N ⊨ T :=
  h.theory_model_iff.1 MT
#align first_order.language.elementarily_equivalent.Theory_model FirstOrder.Language.ElementarilyEquivalent.theory_model

theorem nonempty_iff (h : M ≅[L] N) : Nonempty M ↔ Nonempty N :=
  (model_nonemptyTheory_iff L).symm.trans (h.theory_model_iff.trans (model_nonemptyTheory_iff L))
#align first_order.language.elementarily_equivalent.nonempty_iff FirstOrder.Language.ElementarilyEquivalent.nonempty_iff

theorem nonempty [Mn : Nonempty M] (h : M ≅[L] N) : Nonempty N :=
  h.nonempty_iff.1 Mn
#align first_order.language.elementarily_equivalent.nonempty FirstOrder.Language.ElementarilyEquivalent.nonempty

theorem infinite_iff (h : M ≅[L] N) : Infinite M ↔ Infinite N :=
  (model_infiniteTheory_iff L).symm.trans (h.theory_model_iff.trans (model_infiniteTheory_iff L))
#align first_order.language.elementarily_equivalent.infinite_iff FirstOrder.Language.ElementarilyEquivalent.infinite_iff

theorem infinite [Mi : Infinite M] (h : M ≅[L] N) : Infinite N :=
  h.infinite_iff.1 Mi
#align first_order.language.elementarily_equivalent.infinite FirstOrder.Language.ElementarilyEquivalent.infinite

end ElementarilyEquivalent

end Language

end FirstOrder