import linear_algebra.matrix
import group_theory.free_abelian_group
import algebra.direct_sum.basic
import algebra.big_operators.finsupp
import data.matrix.dmatrix

import for_mathlib.free_abelian_group
import data.matrix.kronecker

import hacks_and_tricks.type_pow
import hacks_and_tricks.by_exactI_hack

/-!
# Breen-Deligne resolutions

Reference:
https://www.math.uni-bonn.de/people/scholze/Condensed.pdf#section*.4
("Appendix to Lecture IV", p. 28)

We formalize the notion of `breen_deligne_data`.
Roughly speaking, this is a collection of formal finite sums of matrices
that encode that data that rolls out of the Breen--Deligne resolution.

## Main definitions

- `breen_deligne.basic_universal_map` : the map corresponding to a matrix
- `breen_deligne.universal_map` : a formal linear combination of basic universal maps.

-/
noncomputable theory

-- get some notation working:
open_locale big_operators direct_sum kronecker

local attribute [instance] type_pow
local notation `ℤ[` A `]` := free_abelian_group A

namespace breen_deligne
open free_abelian_group

/-!
Suppose you have an abelian group `A`.
What data do you need to specify a "universal" map `f : ℤ[A^m] → ℤ[A^n]`?
That is, it should be functorial in `A`.

Well, such a map is specified by what it does to `(a 1, a 2, a 3, ..., a m)`.
It can send this element to an arbitrary element of `ℤ[A^n]`,
but it has to be "universal".

In the end, this means that `f` will be a `ℤ`-linear combination of
"basic universal maps", where a "basic universal map" is one that
sends `(a 1, a 2, ..., a m)` to `(b 1, ..., b n)`,
where `b i` is a `ℤ`-linear combination `c i 1 * a 1 + ... + c i m * a m`.
So a "basic universal map" is specified by the `n × m`-matrix `c`.
-/

/-- A `basic_universal_map m n` is an `n × m`-matrix.
It captures data for a homomorphism `ℤ[A^m] → ℤ[A^n]`
functorial in the abelian group `A`.

A general such homomorphism is a formal linear combination
of `basic_universal_map`s, which we aptly call `universal_map`s. -/
@[derive add_comm_group]
def basic_universal_map (m n : ℕ) := matrix (fin n) (fin m) ℤ

namespace basic_universal_map

variables (A : Type*) [add_comm_group A]
variables {k l m n : ℕ} (g : basic_universal_map m n) (f : basic_universal_map l m)

def pre_eval : basic_universal_map m n →+ A^m → A^n :=
add_monoid_hom.mk' (λ f x i, ∑ j, f i j • (x : fin _ → A) j)
begin
  intros f₁ f₂,
  ext x i,
  simp only [pi.add_apply, dmatrix.add_apply, add_smul, finset.sum_add_distrib],
end

lemma pre_eval_apply : pre_eval A g = λ x i, ∑ j, g i j • (x : fin _ → A) j := rfl

/-- `f.eval A` for a `f : basic_universal_map m n`
is the homomorphism `ℤ[A^m] →+ ℤ[A^n]` induced by matrix multiplication. -/
def eval : ℤ[A^m] →+ ℤ[A^n] :=
map $ pre_eval A g

lemma eval_of (x : A^m) :
  g.eval A (of x) = (of $ pre_eval A g x) :=
lift.of _ _

/-- The composition of basic universal maps,
defined as matrix multiplication. -/
def comp : basic_universal_map m n →+ basic_universal_map l m →+ basic_universal_map l n :=
add_monoid_hom.mk' (λ g, add_monoid_hom.mk' (λ f, matrix.mul g f) $ matrix.mul_add _) $
  λ g₁ g₂, by { ext1 f, apply matrix.add_mul }

lemma eval_comp : (comp g f).eval A = (g.eval A).comp (f.eval A) :=
begin
  ext1 x,
  simp only [add_monoid_hom.coe_comp, function.comp_app, eval_of, pre_eval, comp, finset.smul_sum,
    matrix.mul_apply, finset.sum_smul, mul_smul, add_monoid_hom.mk'_apply],
  congr' 1,
  ext1 i,
  exact finset.sum_comm
end

lemma comp_assoc
  (h : basic_universal_map m n) (g : basic_universal_map l m) (f : basic_universal_map k l) :
  comp (comp h g) f = comp h (comp g f) :=
matrix.mul_assoc h g f

/-- The identity `basic_universal_map`. -/
def id (n : ℕ) : basic_universal_map n n := (1 : matrix (fin n) (fin n) ℤ)

@[simp] lemma id_comp : comp (id _) f = f :=
matrix.one_mul f

@[simp] lemma comp_id : comp g (id _) = g :=
matrix.mul_one g

def mul (N : ℕ) : basic_universal_map m n →+ basic_universal_map (N * m) (N * n) :=
add_monoid_hom.mk'
 (λ f, matrix.reindex_linear_equiv ℕ _ fin_prod_fin_equiv fin_prod_fin_equiv (1 ⊗ₖ f))
begin
  intros f g,
  simp only [matrix.kronecker_add, matrix.reindex_linear_equiv_apply,
    matrix.reindex_apply, matrix.submatrix_add, dmatrix.add_apply],
end

lemma mul_apply (N : ℕ) (f : basic_universal_map m n) :
  mul N f = matrix.reindex_linear_equiv ℕ _ fin_prod_fin_equiv fin_prod_fin_equiv (1 ⊗ₖ f) :=
rfl

lemma mul_injective (N : ℕ) (hN : 0 < N) : function.injective (@mul m n N) :=
begin
  intros f g H,
  ext i j,
  rw function.funext_iff at H,
  specialize H (fin_prod_fin_equiv (⟨0, hN⟩, i)),
  rw function.funext_iff at H,
  specialize H (fin_prod_fin_equiv (⟨0, hN⟩, j)),
  dsimp only [basic_universal_map.mul, matrix.kronecker_map, add_monoid_hom.mk'_apply,
    matrix.reindex_linear_equiv_apply, matrix.reindex_apply, matrix.submatrix_apply] at H,
  simpa only [one_mul, equiv.symm_apply_apply, matrix.one_apply_eq] using H,
end

lemma mul_comp (N : ℕ) (g : basic_universal_map m n) (f : basic_universal_map l m) :
  mul N (comp g f) = comp (mul N g) (mul N f) :=
begin
  ext1 i j,
  dsimp only [mul, comp, add_monoid_hom.mk'_apply],
  rw [matrix.reindex_linear_equiv_mul, ← matrix.mul_kronecker_mul, matrix.one_mul],
end

def one_mul_hom (n) : basic_universal_map (1 * n) n :=
matrix.reindex_linear_equiv ℕ _
  ((fin_one_equiv.prod_congr $ equiv.refl _).trans $ equiv.punit_prod _)
  fin_prod_fin_equiv
  (1 : matrix (fin 1 × fin n) _ ℤ)

def one_mul_inv (n) : basic_universal_map n (1 * n) :=
matrix.reindex_linear_equiv ℕ _
  fin_prod_fin_equiv
  ((fin_one_equiv.prod_congr $ equiv.refl _).trans $ equiv.punit_prod _)
  (1 : matrix (fin 1 × fin n) _ ℤ)

lemma one_mul_hom_inv : comp (one_mul_hom n) (one_mul_inv n) = id n :=
begin
  dsimp only [comp, one_mul_hom, one_mul_inv, add_monoid_hom.mk'_apply, id],
  rw [matrix.reindex_linear_equiv_mul, matrix.one_mul, matrix.reindex_linear_equiv_one],
end

lemma one_mul_inv_hom : comp (one_mul_inv n) (one_mul_hom n) = id _ :=
begin
  dsimp only [comp, one_mul_hom, one_mul_inv, add_monoid_hom.mk'_apply, id],
  rw [matrix.reindex_linear_equiv_mul, matrix.one_mul, matrix.reindex_linear_equiv_one],
end

def mul_mul_hom (m n i : ℕ) : basic_universal_map (m * (n * i)) ((m * n) * i) :=
matrix.reindex_linear_equiv ℕ _
  (((equiv.refl _).prod_congr fin_prod_fin_equiv.symm).trans $
    (equiv.prod_assoc _ _ _).symm.trans $ (fin_prod_fin_equiv.prod_congr $ equiv.refl _).trans
      fin_prod_fin_equiv)
  fin_prod_fin_equiv
  (1 : matrix (fin m × fin (n * i)) (fin m × fin (n * i)) ℤ)

def mul_mul_inv (m n i : ℕ) : basic_universal_map ((m * n) * i) (m * (n * i)) :=
matrix.reindex_linear_equiv ℕ _
  fin_prod_fin_equiv
  (((equiv.refl _).prod_congr fin_prod_fin_equiv.symm).trans $
    (equiv.prod_assoc _ _ _).symm.trans $ (fin_prod_fin_equiv.prod_congr $ equiv.refl _).trans
      fin_prod_fin_equiv)
  (1 : matrix (fin m × fin (n * i)) (fin m × fin (n * i)) ℤ)

lemma mul_mul_hom_inv {m n i : ℕ} : comp (mul_mul_hom m n i) (mul_mul_inv m n i) = id _ :=
begin
  dsimp only [comp, mul_mul_hom, mul_mul_inv, add_monoid_hom.mk'_apply, id],
  rw [matrix.reindex_linear_equiv_mul, matrix.one_mul, matrix.reindex_linear_equiv_one],
end

lemma mul_mul_inv_hom {m n i : ℕ} : comp (mul_mul_inv m n i) (mul_mul_hom m n i) = id _ :=
begin
  dsimp only [comp, mul_mul_hom, mul_mul_inv, add_monoid_hom.mk'_apply, id],
  rw [matrix.reindex_linear_equiv_mul, matrix.one_mul, matrix.reindex_linear_equiv_one],
end

def proj_aux {N : ℕ} (k : fin N) : matrix punit.{1} (fin N) ℤ :=
λ i j, if j = k then 1 else 0

def proj (n : ℕ) {N : ℕ} (k : fin N) : basic_universal_map (N * n) n :=
matrix.reindex_linear_equiv ℕ _ (equiv.punit_prod _) fin_prod_fin_equiv $
(proj_aux k) ⊗ₖ 1

lemma proj_comp_mul {N : ℕ} (k : fin N) (f : basic_universal_map m n) :
  comp (proj n k) (mul N f) = comp f (proj m k) :=
begin
  dsimp only [comp, proj, mul, add_monoid_hom.mk'_apply],
  have : f = (matrix.reindex_linear_equiv
  ℕ _   (equiv.punit_prod (fin n)) (equiv.punit_prod (fin m)))
    ((1 : matrix punit.{1} punit.{1} ℤ) ⊗ₖ f),
  { ext i j,
    simp only [matrix.reindex_linear_equiv_apply, matrix.reindex_apply, matrix.submatrix_apply,
      equiv.punit_prod_symm_apply, matrix.kronecker, matrix.kronecker_apply,
      matrix.one_apply_eq, one_mul] },
  conv_rhs { rw this },
  rw [matrix.reindex_linear_equiv_mul, matrix.reindex_linear_equiv_mul],
  simp only [←matrix.mul_kronecker_mul, matrix.kronecker, matrix.mul_one, matrix.one_mul],
end

lemma one_mul_hom_eq_proj : basic_universal_map.one_mul_hom n = basic_universal_map.proj n 0 :=
begin
  dsimp only [basic_universal_map.one_mul_hom, basic_universal_map.proj],
  rw [← linear_equiv.symm_apply_eq, matrix.reindex_linear_equiv_symm,
    matrix.reindex_linear_equiv_comp_apply, equiv.self_trans_symm, equiv.trans_assoc,
    equiv.self_trans_symm, equiv.trans_refl],
  ext ⟨i, i'⟩ ⟨j, j'⟩ : 2,
  change fin 1 at j,
  dsimp only [matrix.reindex_linear_equiv_apply, matrix.kronecker],
  dsimp [fin_one_equiv, equiv.prod_congr_left, basic_universal_map.proj_aux],
  simp only [matrix.one_apply, prod.mk.inj_iff, @eq_comm _ _ j],
  simp only [true_and, mul_boole, if_true, prod.mk.inj_iff,
    eq_self_iff_true, eq_iff_true_of_subsingleton],
end
.

lemma proj_aux_kronecker_proj_aux (a : fin m) (b : fin n) :
  (proj_aux a) ⊗ₖ (proj_aux b) =
  matrix.reindex_linear_equiv ℕ _ (equiv.prod_punit _).symm fin_prod_fin_equiv.symm
    (proj_aux (fin_prod_fin_equiv (a,b))) :=
begin
  ext ⟨i, i'⟩ ⟨j, j'⟩ : 2,
  dsimp [matrix.reindex_linear_equiv_apply, matrix.kronecker, proj_aux],
  simp only [equiv.apply_eq_iff_eq, boole_mul, prod.mk.inj_iff, ← ite_and],
end

lemma proj_aux_apply (a : fin m) (b : fin m) : proj_aux a punit.star b = ite (a = b) 1 0 :=
begin
  by_cases h : a = b,
  rw if_pos,
  assumption',
  all_goals { simp only [proj_aux, zero_ne_one, ite_eq_left_iff], finish },
end

lemma comp_proj_mul_proj (n N : ℕ) (j : fin (2 * 2 ^ N)) :
  (comp (proj n ((fin_prod_fin_equiv.symm) j).fst)) (mul 2 (proj n ((fin_prod_fin_equiv.symm) j).snd)) =
  (comp (proj n j)) (mul_mul_hom 2 (2 ^ N) n) :=
begin
  dsimp only [mul_mul_hom, proj, comp, mul_apply, add_monoid_hom.mk'_apply],
  rw [matrix.reindex_linear_equiv_mul, ← matrix.mul_kronecker_mul, matrix.one_mul,
    matrix.mul_one, matrix.mul_reindex_linear_equiv_one],
  simp only [matrix.reindex_linear_equiv_apply, matrix.reindex_apply, function.comp.right_id,
    matrix.submatrix_submatrix, equiv.refl_symm, equiv.coe_refl],
  ext x y,
  dsimp [matrix.submatrix, - fin_prod_fin_equiv_symm_apply],
  rw [matrix.one_apply, matrix.one_apply],
  simp only [mul_boole, mul_ite, mul_zero, equiv.symm_apply_apply, mul_one],
  congr' 2,
  simp only [proj_aux_apply, boole_mul],
  rw [← ite_and],
  congr' 1,
  rw [← prod.mk.inj_iff, prod.mk.eta, equiv.symm_apply_eq],
end

end basic_universal_map

/-- A `universal_map m n` is a formal `ℤ`-linear combination
of `basic_universal_map`s.
It captures the data for a homomorphism `ℤ[A^m] → ℤ[A^n]`. -/
@[derive add_comm_group]
def universal_map (m n : ℕ) := ℤ[basic_universal_map m n]

namespace universal_map
universe variable u

variables {k l m n : ℕ} (g : universal_map m n) (f : universal_map l m)
variables (A : Type u) [add_comm_group A]

/-- `f.eval A` for a `f : universal_map m n`
is the homomorphism `ℤ[A^m] →+ ℤ[A^n]` induced by matrix multiplication
of the summands occurring in the formal linear combination `f`. -/
def eval : universal_map m n →+ ℤ[A^m] →+ ℤ[A^n] :=
free_abelian_group.lift $ λ (f : basic_universal_map m n), f.eval A

@[simp] lemma eval_of (f : basic_universal_map m n) :
  eval A (of f) = f.eval A :=
lift.of _ _

/-- The composition of `universal_map`s `g` and `f`,
given by the formal linear combination of all compositions
of summands occurring in `g` and `f`. -/
def comp : universal_map m n →+ universal_map l m →+ universal_map l n :=
free_abelian_group.lift $ λ (g : basic_universal_map m n), free_abelian_group.lift $ λ f,
of $ basic_universal_map.comp g f

@[simp] lemma comp_of (g : basic_universal_map m n) (f : basic_universal_map l m) :
  comp (of g) (of f) = of (basic_universal_map.comp g f) :=
by rw [comp, lift.of, lift.of]

section
open add_monoid_hom

lemma eval_comp : eval A (comp g f) = (eval A g).comp (eval A f) :=
show comp_hom (comp_hom (@eval l n A _)) (comp) g f =
  comp_hom (comp_hom (comp_hom.flip (@eval l m A _)) (comp_hom)) (@eval m n A _) g f,
begin
  congr' 2, clear f g, ext g f : 2,
  show eval A (comp (of g) (of f)) = (eval A (of g)).comp (eval A (of f)),
  simp only [basic_universal_map.eval_comp, comp_of, eval_of]
end

lemma comp_assoc (h : universal_map m n) (g : universal_map l m) (f : universal_map k l) :
  comp (comp h g) f = comp h (comp g f) :=
show comp_hom (comp_hom (@comp k l n)) (@comp l m n) h g f =
     comp_hom (comp_hom (comp_hom.flip (@comp k l m)) (comp_hom)) (@comp k m n) h g f,
begin
  congr' 3, clear h g f, ext h g f : 3,
  show comp (comp (of h) (of g)) (of f) = comp (of h) (comp (of g) (of f)),
  simp only [basic_universal_map.comp_assoc, comp_of]
end

/-- The identity `universal_map`. -/
def id (n : ℕ) : universal_map n n := of (basic_universal_map.id n)

@[simp] lemma id_comp : comp (id _) f = f :=
show comp (id _) f = add_monoid_hom.id _ f,
begin
  congr' 1, clear f, ext1 f,
  simp only [id, comp_of, id_apply, basic_universal_map.id_comp]
end

@[simp] lemma comp_id : comp g (id _) = g :=
show (@comp m m n).flip (id _) g = add_monoid_hom.id _ g,
begin
  congr' 1, clear g, ext1 g,
  show comp (of g) (id _) = (of g),
  simp only [id, comp_of, id_apply, basic_universal_map.comp_id]
end

def bound : ℕ := ∑ g in f.support, (free_abelian_group.coeff g f).nat_abs

def bound_by (N : ℕ) : Prop := f.bound ≤ N

lemma of_bound_by (f : basic_universal_map m n) : bound_by (of f) 1 :=
begin
  simp only [bound_by, bound, coeff_of_self, int.nat_abs_one, finset.sum_singleton, support_of],
end

lemma zero_bound_by (N : ℕ) : (0 : universal_map m n).bound_by N :=
by simp only [bound_by, bound, zero_le', finset.sum_const_zero,
    add_monoid_hom.map_zero, int.nat_abs_zero]

lemma zero_bound_by_zero : (0 : universal_map m n).bound_by 0 :=
zero_bound_by _

lemma bound_by.random_index {f : universal_map m n} {N : ℕ}
  (hf : f.bound_by N) (s : finset (basic_universal_map m n)) :
  ∑ g in s, (free_abelian_group.coeff g f).nat_abs ≤ N :=
begin
  calc ∑ g in s, (free_abelian_group.coeff g f).nat_abs
      = ∑ g in s ∩ f.support, (free_abelian_group.coeff g f).nat_abs +
        ∑ g in s \ f.support, (free_abelian_group.coeff g f).nat_abs : _
  ... = ∑ g in s ∩ f.support, (free_abelian_group.coeff g f).nat_abs : _
  ... ≤ ∑ g in f.support ∩ s, (free_abelian_group.coeff g f).nat_abs +
        ∑ g in f.support \ s, (free_abelian_group.coeff g f).nat_abs : _
  ... ≤ ∑ g in f.support, (free_abelian_group.coeff g f).nat_abs : _
  ... ≤ N : hf,
  { rw finset.sum_inter_add_sum_diff },
  { simp only [and_imp, add_right_eq_self, int.nat_abs_eq_zero, imp_self, imp_true_iff,
      finset.mem_sdiff, finset.sum_eq_zero_iff, free_abelian_group.not_mem_support_iff] },
  { rw finset.inter_comm, simp only [le_add_iff_nonneg_right, zero_le'], },
  { rw finset.sum_inter_add_sum_diff },
end

lemma bound_by.add {f₁ f₂ : universal_map m n} {N₁ N₂ : ℕ}
  (h₁ : f₁.bound_by N₁) (h₂ : f₂.bound_by N₂) :
  (f₁ + f₂).bound_by (N₁ + N₂) :=
begin
  calc (f₁ + f₂).bound ≤
      ∑ (g : basic_universal_map m n) in support (f₁ + f₂),
        ((coeff g f₁).nat_abs + (coeff g f₂).nat_abs) : finset.sum_le_sum _
  ... ≤ N₁ + N₂ : _,
  { intros g hg,
    rw add_monoid_hom.map_add,
    apply int.nat_abs_add_le },
  { rw finset.sum_add_distrib,
    exact add_le_add (h₁.random_index _) (h₂.random_index _) }
end

lemma bound_by_sum {ι : Type*} (s : finset ι) (f : ι → universal_map m n) (N : ι → ℕ)
  (h : ∀ i ∈ s, (f i).bound_by (N i)) :
  (∑ i in s, f i).bound_by (∑ i in s, N i) :=
begin
  classical,
  revert h,
  apply finset.induction_on s; clear s,
  { simp only [finset.not_mem_empty, is_empty.forall_iff, finset.sum_empty,
      implies_true_iff, forall_true_left],
    exact zero_bound_by_zero },
  { intros i s his IH h,
    simp only [finset.sum_insert his],
    exact (h i $ s.mem_insert_self i).add (IH $ λ j hj, h j $ finset.mem_insert_of_mem hj) }
end

end

section mul
open add_monoid_hom

/-
TODO: refactor `mul` to be a functor
TODO: put a monoidal structure on `FreeMat`, so that this is just `N ⊗ _`.
-/

def mul (N : ℕ) : universal_map m n →+ universal_map (N * m) (N * n) :=
map (basic_universal_map.mul N)

lemma mul_of (N : ℕ) (f : basic_universal_map m n) :
  mul N (of f) = of (basic_universal_map.mul N f) :=
map_of_apply _

lemma mul_comp (N : ℕ) (g : universal_map m n) (f : universal_map l m) :
  mul N (comp g f) = comp (mul N g) (mul N f) :=
begin
  simp only [← add_monoid_hom.comp_apply],
  rw [← add_monoid_hom.comp_hom_apply_apply, ← add_monoid_hom.comp_hom_apply_apply,
    ← add_monoid_hom.comp_hom_apply_apply,
    ← add_monoid_hom.flip_apply _ _ (mul N)],
  simp only [← add_monoid_hom.comp_apply],
  rw [← add_monoid_hom.comp_hom_apply_apply, ← add_monoid_hom.comp_hom_apply_apply],
  congr' 2, clear f g, ext g f,
  show (mul N) ((comp (of g)) (of f)) = (comp ((mul N) (of g))) ((mul N) (of f)),
  simp only [comp_of, mul_of, basic_universal_map.mul_comp],
end

lemma mem_support_mul (N : ℕ) (hN : 0 < N) (f : universal_map m n) (g) :
  g ∈ (mul N f).support ↔ ∃ g', g' ∈ f.support ∧ g = basic_universal_map.mul N g' :=
begin
  apply free_abelian_group.mem_support_map,
  exact basic_universal_map.mul_injective N hN
end

@[simp]
lemma coeff_mul (N : ℕ) (hN : 0 < N) (f : universal_map m n) (g : basic_universal_map m n) :
  coeff (basic_universal_map.mul N g) (mul N f) = coeff g f :=
begin
  simp only [← add_monoid_hom.comp_apply],
  rw [← add_monoid_hom.comp_hom_apply_apply],
  congr' 1, clear f, ext f,
  simp only [comp_hom_apply_apply, function.comp_app, coe_comp, mul, coeff, to_finsupp_of,
    map_of_apply, finsupp.apply_add_hom_apply, finsupp.single_apply,
    (basic_universal_map.mul_injective N hN).eq_iff],
end

end mul

/-
TODO: refactor `sum` and `proj` to be natural transformations from `mul n` to `𝟭 _`.
-/

def sum (n N : ℕ) : universal_map (N * n) n :=
of (∑ i, basic_universal_map.proj n i)

def proj (n N : ℕ) : universal_map (N * n) n :=
∑ i, of (basic_universal_map.proj n i)

lemma sum_comp_mul (N : ℕ) (f : universal_map m n) :
  comp (sum n N) (mul N f) = comp f (sum m N) :=
begin
  simp only [← add_monoid_hom.comp_apply],
  rw [← add_monoid_hom.comp_hom_apply_apply, ← add_monoid_hom.flip_apply _ _ (sum m N)],
  simp only [← add_monoid_hom.comp_apply],
  congr' 1, clear f, ext f,
  show (comp (sum n N)) ((mul N) (of f)) = (comp (of f)) (sum m N),
  simp only [sum, mul_of, comp_of, add_monoid_hom.map_sum,
    add_monoid_hom.finset_sum_apply, basic_universal_map.proj_comp_mul],
end

lemma proj_comp_mul (N : ℕ) (f : universal_map m n) :
  comp (proj n N) (mul N f) = comp f (proj m N) :=
begin
  simp only [← add_monoid_hom.comp_apply],
  rw [← add_monoid_hom.comp_hom_apply_apply, ← add_monoid_hom.flip_apply _ _ (proj m N)],
  simp only [← add_monoid_hom.comp_apply],
  congr' 1, clear f, ext f,
  show (comp (proj n N)) ((mul N) (of f)) = (comp (of f)) (proj m N),
  simp only [proj, mul_of, comp_of, add_monoid_hom.map_sum,
    add_monoid_hom.finset_sum_apply, basic_universal_map.proj_comp_mul],
end
.

lemma proj_bound_by (n N : ℕ) : (proj n N).bound_by N :=
le_trans (bound_by_sum _ _ _ $ λ i _, of_bound_by _) $
by simp only [finset.card_fin, mul_one, algebra.id.smul_eq_mul, finset.sum_const]

end universal_map

end breen_deligne

-- #lint- only unused_arguments def_lemma doc_blame


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Nathaniel Thomas

More examples to test automation, stolen shamelessly by Jeremy from Nathaniel's "tauto".
-/
import tactic.finish
open nat

section

variables (a b c d e f : Prop)
variable even : ℕ → Prop
variable P : ℕ → Prop

-- these next five are things that tauto doesn't get

example : (∀ x, P x) ∧ b → (∀ y, P y) ∧ P 0 ∨ b ∧ P 0 := by finish
example : (∀ A, A ∨ ¬A) → ∀ x y : ℕ, x = y ∨ x ≠ y := by finish
example : ∀ b1 b2, b1 = b2 ↔ (b1 = tt ↔ b2 = tt) := begin intro b1, cases b1; finish [iff_def] end

example : ∀ (P Q : nat → Prop), (∀ n, Q n → P n) → (∀ n, Q n) → P 2 := by finish
example (a b c : Prop) : ¬ true ∨ false ∨ b ↔ b := by finish

example : true := by finish

example : false → a := by finish
example : a → a := by finish
example : (a → b) → a → b := by finish
example : ¬ a → ¬ a := by finish
example : a → (false ∨ a) := by finish
example : (a → b → c) → (a → b) → a → c := by finish
example : a → ¬ a → (a → b) → (a ∨ b) → (a ∧ b) → a → false := by finish
example : ((a ∧ b) ∧ c) → b := by finish
example : ((a → b) → c) → b → c := by finish
example : (a ∨ b) → (b ∨ a) := by finish
example : (a → b ∧ c) → (a → b) ∨ (a → c) := by finish
example : ∀ (x0 : a ∨ b) (x1 : b ∧ c), a → b := by finish
example : a → b → (c ∨ b) := by finish
example : (a ∧ b → c) → b → a → c := by finish
example : (a ∨ b → c) → a → c := by finish
example : (a ∨ b → c) → b → c := by finish
example : (a ∧ b) → (b ∧ a) := by finish
example : (a ↔ b) → a → b := by finish
example : a → ¬¬a := by finish
example : ¬¬(a ∨ ¬a) := by finish
example : ¬¬(a ∨ b → a ∨ b) := by finish
example : ¬¬((∀ n, even n) ∨ ¬(∀ m, even m)) := by finish
example : (¬¬b → b) → (a → b) → ¬¬a → b := by finish
example : (¬¬b → b) → (¬b → ¬ a) → ¬¬a → b := by finish

example : ((a → b → false) → false) → (b → false) → false := by finish

example : ((((c → false) → a) → ((b → false) → a) → false) → false) →
            (((c → b → false) → false) → false) → ¬a → a := by finish

example (p q r : Prop) (a b : nat) : true → a = a → q → q → p → p := by finish
example : ∀ (F F' : Prop), F ∧ F' → F := by finish
example : ∀ (F1 F2 F3 : Prop), ((¬F1 ∧ F3) ∨ (F2 ∧ ¬F3)) → (F2 → F1) → (F2 → F3) →  ¬F2 := by finish
example : ∀ (f : nat → Prop), f 2 → ∃ x, f x := by finish
example : true ∧ true ∧ true ∧ true ∧ true ∧ true ∧ true := by finish
example : ∀ (P : nat → Prop), P 0 → (P 0 → P 1) → (P 1 → P 2) → (P 2) := by finish
example : ¬¬¬¬¬a → ¬¬¬¬¬¬¬¬a → false := by finish
example : ∀ n, ¬¬(even n ∨ ¬even n) := by finish
example : ∀ (p q r s : Prop) (a b : nat), r ∨ s → p ∨ q → a = b → q ∨ p := by finish
example : (∀ x, P x) → (∀ y, P y) := by finish

/- TODO(Jeremy): reinstate after simp * at * bug is fixed.
example : ((a ↔ b) → (b ↔ c)) → ((b ↔ c) → (c ↔ a)) → ((c ↔ a) → (a ↔ b)) → (a ↔ b) :=
by finish [iff_def]
-/

example : ((¬a ∨ b) ∧ (¬b ∨ b) ∧ (¬a ∨ ¬b) ∧ (¬b ∨ ¬b) → false) → ¬((a → b) → b) → false := by finish

example : ¬((a → b) → b) → ((¬b ∨ ¬b) ∧ (¬b ∨ ¬a) ∧ (b ∨ ¬b) ∧ (b ∨ ¬a) → false) → false := by finish
example : (¬a ↔ b) → (¬b ↔ a) → (¬¬a ↔ a) := by finish

example : (¬ a ↔ b) → (¬ (c ∨ e) ↔ d ∧ f) → (¬ (c ∨ a ∨ e) ↔ d ∧ b ∧ f) := by finish

example {A : Type} (p q : A → Prop) (a b : A) : q a → p b → ∃ x, (p x ∧ x = b) ∨ q x := by finish

example {A : Type} (p q : A → Prop) (a b : A) : p b → ∃ x, q x ∨ (p x ∧ x = b) := by finish

example : ¬ a → b → a → c := by finish
example : a → b → b → ¬ a → c := by finish
example (a b : nat) : a = b → b = a := by finish

-- good examples of things we don't get, even using the simplifier
example (a b c : nat) : a = b → a = c → b = c := by finish
example (p : nat → Prop) (a b c : nat) : a = b → a = c → p b → p c := by finish

example (p : Prop) (a b : nat) : a = b → p → p := by finish

-- safe should look for contradictions with constructors
example (a : nat) : (0 : ℕ) = succ a → a = a → false := by finish
example (p : Prop) (a b c : nat) : [a, b, c] = [] → p := by finish

example (a b c : nat) : succ (succ a) = succ (succ b) → c = c := by finish
example (p : Prop) (a b : nat) : a = b → b ≠ a → p := by finish
example : (a ↔ b) → ((b ↔ a) ↔ (a ↔ b)) := by finish
example (a b c : nat) : b = c → (a = b ↔ c = a) := by finish [iff_def]
example : ¬¬¬¬¬¬¬¬a → ¬¬¬¬¬a → false := by finish
example (a b c : Prop) : a ∧ b ∧ c ↔ c ∧ b ∧ a := by finish
example (a b c : Prop) : a ∧ false ∧ c ↔ false := by finish
example (a b c : Prop) : a ∨ false ∨ b ↔ b ∨ a := by finish
example : a ∧ not a ↔ false := by finish
example : a ∧ b ∧ true → b ∧ a := by finish
example (A : Type) (a₁ a₂ : A) : a₁ = a₂ →
  (λ (B : Type) (f : A → B), f a₁) = (λ (B : Type) (f : A → B), f a₂) := by finish
example (a : nat) : ¬ a = a → false := by finish
example (A : Type) (p : Prop) (a b c : A) : a = b → b ≠ a → p := by finish
example (p q r s : Prop) : r ∧ s → p ∧ q → q ∧ p := by finish
example (p q : Prop) : p ∧ p ∧ q ∧ q → q ∧ p := by finish
example (p : nat → Prop) (q : nat → nat → Prop) :
  (∃ x y, p x ∧ q x y) → q 0 0 ∧ q 1 1 → (∃ x, p x) := by finish
example (p q r s : Prop) (a b : nat) : r ∨ s → p ∨ q → a = b → q ∨ p := by finish
example (p q r : Prop) (a b : nat) : true → a = a → q → q → p → p := by finish
example (a b : Prop) : a → b → a := by finish
example (p q : nat → Prop) (a b : nat) : p a → q b → ∃ x, p x := by finish

example : ∀ b1 b2, b1 && b2 = ff ↔ (b1 = ff ∨ b2 = ff) := by finish
example : ∀ b1 b2, b1 && b2 = tt ↔ (b1 = tt ∧ b2 = tt) := by finish
example : ∀ b1 b2, b1 || b2 = ff ↔ (b1 = ff ∧ b2 = ff) := by finish
example : ∀ b1 b2, b1 || b2 = tt ↔ (b1 = tt ∨ b2 = tt) := by finish
example : ∀ b, bnot b = tt ↔ b = ff := by finish
example : ∀ b, bnot b = ff ↔ b = tt := by finish
example : ∀ b c, b = c ↔ ¬ (b = bnot c) := by intros b c; cases b; cases c; finish [iff_def]

inductive and3 (a b c : Prop) : Prop
| mk : a → b → c → and3

example (h : and3 a b c) : and3 b c a := by cases h; split; finish

inductive or3 (a b c : Prop) : Prop
| in1 : a → or3
| in2 : b → or3
| in3 : c → or3

/- TODO(Jeremy): write a tactic that tries all constructors
example (h : a) : or3 a b c := sorry
example (h : b) : or3 a b c := sorry
example (h : c) : or3 a b c := sorry
-/

variables (A₁ A₂ A₃ A₄ B₁ B₂ B₃ B₄ : Prop)
-- H first, all pos

example (H1 : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) : B₄ := by finish
example (H1 : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₄) : B₃ := by finish
example (H1 : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n3 : ¬B₃) (n3 : ¬B₄) : B₂ := by finish
example (H1 : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a2 : A₂) (a3 : A₃) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄) : B₁ := by finish

example (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a2 : A₂) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄) : ¬A₃ := by finish
example (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a1 : A₁) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄) : ¬A₂ := by finish
example (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄)
  (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄) : ¬A₁ := by finish

-- H last, all pos
example (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : B₄ := by finish
example (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : B₃ := by finish
example (a1 : A₁) (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n3 : ¬B₃) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : B₂ := by finish
example (a1 : A₁) (a2 : A₂) (a3 : A₃) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : B₁ := by finish

example (a1 : A₁) (a2 : A₂) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : ¬A₃ := by finish
example (a1 : A₁) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : ¬A₂ := by finish
example (a2 : A₂) (a3 : A₃) (n1 : ¬B₁) (n2 : ¬B₂) (n3 : ¬B₃) (n3 : ¬B₄)
  (H : A₁ → A₂ → A₃ → B₁ ∨ B₂ ∨ B₃ ∨ B₄) : ¬A₁ := by finish

-- H first, all neg
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃) : ¬B₄ := by finish
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b4 : B₄) : ¬B₃ := by finish
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b3 : B₃) (b4 : B₄) : ¬B₂ := by finish
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b2 : B₂) (b3 : B₃) (b4 : B₄) : ¬B₁ := by finish

example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n2 : ¬A₂) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄) : ¬¬A₃ := by finish
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n1 : ¬A₁) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄) : ¬¬A₂ := by finish
example (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄)
  (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄) : ¬¬A₁ := by finish

-- H last, all neg
example (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬B₄ := by finish
example (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬B₃ := by finish
example (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b3 : B₃) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬B₂ := by finish
example (n1 : ¬A₁) (n2 : ¬A₂) (n3 : ¬A₃) (b2 : B₂) (b3 : B₃) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬B₁ := by finish

example (n1 : ¬A₁) (n2 : ¬A₂) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬¬A₃ := by finish
example (n1 : ¬A₁) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬¬A₂ := by finish
example (n2 : ¬A₂) (n3 : ¬A₃) (b1 : B₁) (b2 : B₂) (b3 : B₃) (b4 : B₄)
  (H : ¬A₁ → ¬A₂ → ¬A₃ → ¬B₁ ∨ ¬B₂ ∨ ¬B₃ ∨ ¬B₄) : ¬¬A₁ := by finish

section club
variables Scottish RedSocks WearKilt Married GoOutSunday : Prop
theorem NoMember : (¬Scottish → RedSocks) → (WearKilt ∨ ¬RedSocks) → (Married → ¬GoOutSunday) →
                 (GoOutSunday ↔ Scottish) → (WearKilt → Scottish ∧ Married) →
                 (Scottish → WearKilt) → false := by finish
end club

end


/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies

! This file was ported from Lean 3 source module category_theory.category.Bipointed
! leanprover-community/mathlib commit c8ab806ef73c20cab1d87b5157e43a82c205f28e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.CategoryTheory.Category.Pointed

/-!
# The category of bipointed types

This defines `Bipointed`, the category of bipointed types.

## TODO

Monoidal structure
-/

open CategoryTheory

universe u

variable {α β : Type _}

set_option linter.uppercaseLean3 false

/-- The category of bipointed types. -/
structure Bipointed : Type (u + 1) where
  X : Type u
  toProd : X × X
#align Bipointed Bipointed

namespace Bipointed

instance : CoeSort Bipointed (Type _) :=
  ⟨X⟩

-- porting note: protected attribute does not work
-- attribute [protected] Bipointed.X

/-- Turns a bipointing into a bipointed type. -/
def of {X : Type _} (to_prod : X × X) : Bipointed :=
  ⟨X, to_prod⟩
#align Bipointed.of Bipointed.of

@[simp]
theorem coe_of {X : Type _} (to_prod : X × X) : ↥(of to_prod) = X :=
  rfl
#align Bipointed.coe_of Bipointed.coe_of

alias of ← _root_.Prod.Bipointed
#align prod.Bipointed Prod.Bipointed

instance : Inhabited Bipointed :=
  ⟨of ((), ())⟩

/-- Morphisms in `Bipointed`. -/
@[ext]
protected structure Hom (X Y : Bipointed.{u}) : Type u where
  toFun : X → Y
  map_fst : toFun X.toProd.1 = Y.toProd.1
  map_snd : toFun X.toProd.2 = Y.toProd.2
#align Bipointed.hom Bipointed.Hom

namespace Hom

/-- The identity morphism of `X : Bipointed`. -/
@[simps]
nonrec def id (X : Bipointed) : Bipointed.Hom X X :=
  ⟨id, rfl, rfl⟩
#align Bipointed.hom.id Bipointed.Hom.id

instance (X : Bipointed) : Inhabited (Bipointed.Hom X X) :=
  ⟨id X⟩

/-- Composition of morphisms of `Bipointed`. -/
@[simps]
def comp {X Y Z : Bipointed.{u}} (f : Bipointed.Hom X Y) (g : Bipointed.Hom Y Z) :
    Bipointed.Hom X Z :=
  ⟨g.toFun ∘ f.toFun, by rw [Function.comp_apply, f.map_fst, g.map_fst], by
    rw [Function.comp_apply, f.map_snd, g.map_snd]⟩
#align Bipointed.hom.comp Bipointed.Hom.comp

end Hom

instance largeCategory : LargeCategory Bipointed where
  Hom := Bipointed.Hom
  id := Hom.id
  comp := @Hom.comp
#align Bipointed.large_category Bipointed.largeCategory

instance concreteCategory : ConcreteCategory Bipointed where
  Forget :=
    { obj := Bipointed.X
      map := @Hom.toFun }
  forget_faithful := ⟨@Hom.ext⟩
#align Bipointed.concrete_category Bipointed.concreteCategory

/-- Swaps the pointed elements of a bipointed type. `Prod.swap` as a functor. -/
@[simps]
def swap : Bipointed ⥤ Bipointed where
  obj X := ⟨X, X.toProd.swap⟩
  map f := ⟨f.toFun, f.map_snd, f.map_fst⟩
#align Bipointed.swap Bipointed.swap

/-- The equivalence between `Bipointed` and itself induced by `Prod.swap` both ways. -/
@[simps!]
def swapEquiv : Bipointed ≌ Bipointed :=
  CategoryTheory.Equivalence.mk swap swap
    (NatIso.ofComponents
      (fun X =>
        { hom := ⟨id, rfl, rfl⟩
          inv := ⟨id, rfl, rfl⟩ })
      fun f => rfl)
    (NatIso.ofComponents
      (fun X =>
        { hom := ⟨id, rfl, rfl⟩
          inv := ⟨id, rfl, rfl⟩ })
      fun f => rfl)
#align Bipointed.swap_equiv Bipointed.swapEquiv

@[simp]
theorem swapEquiv_symm : swapEquiv.symm = swapEquiv :=
  rfl
#align Bipointed.swap_equiv_symm Bipointed.swapEquiv_symm

end Bipointed

/-- The forgetful functor from `Bipointed` to `Pointed` which forgets about the second point. -/
def bipointedToPointedFst : Bipointed ⥤ Pointed where
  obj X := ⟨X, X.toProd.1⟩
  map f := ⟨f.toFun, f.map_fst⟩
#align Bipointed_to_Pointed_fst bipointedToPointedFst

/-- The forgetful functor from `Bipointed` to `Pointed` which forgets about the first point. -/
def bipointedToPointedSnd : Bipointed ⥤ Pointed where
  obj X := ⟨X, X.toProd.2⟩
  map f := ⟨f.toFun, f.map_snd⟩
#align Bipointed_to_Pointed_snd bipointedToPointedSnd

@[simp]
theorem bipointedToPointedFst_comp_forget :
    bipointedToPointedFst ⋙ forget Pointed = forget Bipointed :=
  rfl
#align Bipointed_to_Pointed_fst_comp_forget bipointedToPointedFst_comp_forget

@[simp]
theorem bipointedToPointedSnd_comp_forget :
    bipointedToPointedSnd ⋙ forget Pointed = forget Bipointed :=
  rfl
#align Bipointed_to_Pointed_snd_comp_forget bipointedToPointedSnd_comp_forget

@[simp]
theorem swap_comp_bipointedToPointedFst :
    Bipointed.swap ⋙ bipointedToPointedFst = bipointedToPointedSnd :=
  rfl
#align swap_comp_Bipointed_to_Pointed_fst swap_comp_bipointedToPointedFst

@[simp]
theorem swap_comp_bipointedToPointedSnd :
    Bipointed.swap ⋙ bipointedToPointedSnd = bipointedToPointedFst :=
  rfl
#align swap_comp_Bipointed_to_Pointed_snd swap_comp_bipointedToPointedSnd

/-- The functor from `Pointed` to `Bipointed` which bipoints the point. -/
def pointedToBipointed : Pointed.{u} ⥤ Bipointed where
  obj X := ⟨X, X.point, X.point⟩
  map f := ⟨f.toFun, f.map_point, f.map_point⟩
#align Pointed_to_Bipointed pointedToBipointed

/-- The functor from `Pointed` to `Bipointed` which adds a second point. -/
def pointedToBipointedFst : Pointed.{u} ⥤ Bipointed where
  obj X := ⟨Option X, X.point, none⟩
  map f := ⟨Option.map f.toFun, congr_arg _ f.map_point, rfl⟩
  map_id _ := Bipointed.Hom.ext _ _ Option.map_id
  map_comp f g := Bipointed.Hom.ext _ _ (Option.map_comp_map f.1 g.1).symm
#align Pointed_to_Bipointed_fst pointedToBipointedFst

/-- The functor from `Pointed` to `Bipointed` which adds a first point. -/
def pointedToBipointedSnd : Pointed.{u} ⥤ Bipointed where
  obj X := ⟨Option X, none, X.point⟩
  map f := ⟨Option.map f.toFun, rfl, congr_arg _ f.map_point⟩
  map_id _ := Bipointed.Hom.ext _ _ Option.map_id
  map_comp f g := Bipointed.Hom.ext _ _ (Option.map_comp_map f.1 g.1).symm
#align Pointed_to_Bipointed_snd pointedToBipointedSnd

@[simp]
theorem pointedToBipointedFst_comp_swap :
    pointedToBipointedFst ⋙ Bipointed.swap = pointedToBipointedSnd :=
  rfl
#align Pointed_to_Bipointed_fst_comp_swap pointedToBipointedFst_comp_swap

@[simp]
theorem pointedToBipointedSnd_comp_swap :
    pointedToBipointedSnd ⋙ Bipointed.swap = pointedToBipointedFst :=
  rfl
#align Pointed_to_Bipointed_snd_comp_swap pointedToBipointedSnd_comp_swap

/-- `BipointedToPointed_fst` is inverse to `PointedToBipointed`. -/
@[simps!]
def pointedToBipointedCompBipointedToPointedFst :
    pointedToBipointed ⋙ bipointedToPointedFst ≅ 𝟭 _ :=
  NatIso.ofComponents
    (fun X =>
      { hom := ⟨id, rfl⟩
        inv := ⟨id, rfl⟩ })
    fun f => rfl
#align Pointed_to_Bipointed_comp_Bipointed_to_Pointed_fst pointedToBipointedCompBipointedToPointedFst

/-- `BipointedToPointed_snd` is inverse to `PointedToBipointed`. -/
@[simps!]
def pointedToBipointedCompBipointedToPointedSnd :
    pointedToBipointed ⋙ bipointedToPointedSnd ≅ 𝟭 _ :=
  NatIso.ofComponents
    (fun X =>
      { hom := ⟨id, rfl⟩
        inv := ⟨id, rfl⟩ })
    fun f => rfl
#align Pointed_to_Bipointed_comp_Bipointed_to_Pointed_snd pointedToBipointedCompBipointedToPointedSnd

/-- The free/forgetful adjunction between `PointedToBipointed_fst` and `BipointedToPointed_fst`.
-/
def pointedToBipointedFstBipointedToPointedFstAdjunction :
    pointedToBipointedFst ⊣ bipointedToPointedFst :=
  Adjunction.mkOfHomEquiv
    { homEquiv := fun X Y =>
        { toFun := fun f => ⟨f.toFun ∘ Option.some, f.map_fst⟩
          invFun := fun f => ⟨fun o => o.elim Y.toProd.2 f.toFun, f.map_point, rfl⟩
          left_inv := fun f => by
            apply Bipointed.Hom.ext
            funext x
            cases x
            · exact f.map_snd.symm
            · rfl
          right_inv := fun f => Pointed.Hom.ext _ _ rfl }
      homEquiv_naturality_left_symm := fun f g => by
        apply Bipointed.Hom.ext
        funext x
        cases x <;> rfl }
#align Pointed_to_Bipointed_fst_Bipointed_to_Pointed_fst_adjunction pointedToBipointedFstBipointedToPointedFstAdjunction

/-- The free/forgetful adjunction between `PointedToBipointed_snd` and `BipointedToPointed_snd`.
-/
def pointedToBipointedSndBipointedToPointedSndAdjunction :
    pointedToBipointedSnd ⊣ bipointedToPointedSnd :=
  Adjunction.mkOfHomEquiv
    { homEquiv := fun X Y =>
        { toFun := fun f => ⟨f.toFun ∘ Option.some, f.map_snd⟩
          invFun := fun f => ⟨fun o => o.elim Y.toProd.1 f.toFun, rfl, f.map_point⟩
          left_inv := fun f => by
            apply Bipointed.Hom.ext
            funext x
            cases x
            · exact f.map_fst.symm
            · rfl
          right_inv := fun f => Pointed.Hom.ext _ _ rfl }
      homEquiv_naturality_left_symm := fun f g => by
        apply Bipointed.Hom.ext
        funext x
        cases x <;> rfl }
#align Pointed_to_Bipointed_snd_Bipointed_to_Pointed_snd_adjunction pointedToBipointedSndBipointedToPointedSndAdjunction


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen

! This file was ported from Lean 3 source module data.fintype.fin
! leanprover-community/mathlib commit 759575657f189ccb424b990164c8b1fa9f55cdfe
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Fin.Interval

/-!
# The structure of `fintype (fin n)`

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file contains some basic results about the `fintype` instance for `fin`,
especially properties of `finset.univ : finset (fin n)`.
-/

open Finset

open Fintype

namespace Fin

variable {α β : Type _} {n : ℕ}

#print Fin.map_valEmbedding_univ /-
-- TODO: replace `subtype` with `coe` in the name of this lemma and `fin.map_subtype_embedding_Iio`
theorem map_valEmbedding_univ : (Finset.univ : Finset (Fin n)).map Fin.valEmbedding = Iio n :=
  by
  ext
  simp [order_iso_subtype.symm.surjective.exists, OrderIso.symm]
#align fin.map_subtype_embedding_univ Fin.map_valEmbedding_univ
-/

/- warning: fin.Ioi_zero_eq_map -> Fin.Ioi_zero_eq_map is a dubious translation:
lean 3 declaration is
  forall {n : Nat}, Eq.{1} (Finset.{0} (Fin (Nat.succ n))) (Finset.Ioi.{0} (Fin (Nat.succ n)) (PartialOrder.toPreorder.{0} (Fin (Nat.succ n)) (Fin.partialOrder (Nat.succ n))) (Fin.locallyFiniteOrderTop (Nat.succ n)) (OfNat.ofNat.{0} (Fin (Nat.succ n)) 0 (OfNat.mk.{0} (Fin (Nat.succ n)) 0 (Zero.zero.{0} (Fin (Nat.succ n)) (Fin.hasZeroOfNeZero (Nat.succ n) (NeZero.succ n)))))) (Finset.map.{0, 0} (Fin n) (Fin (Nat.succ n)) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Preorder.toLE.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (PartialOrder.toPreorder.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.partialOrder (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))))) (Fin.succEmbedding n)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))
but is expected to have type
  forall {n : Nat}, Eq.{1} (Finset.{0} (Fin (Nat.succ n))) (Finset.Ioi.{0} (Fin (Nat.succ n)) (PartialOrder.toPreorder.{0} (Fin (Nat.succ n)) (Fin.instPartialOrderFin (Nat.succ n))) (instForAllNatLocallyFiniteOrderTopFinToPreorderInstPartialOrderFin (Nat.succ n)) (OfNat.ofNat.{0} (Fin (Nat.succ n)) 0 (Fin.instOfNatFin (Nat.succ n) 0 (NeZero.succ n)))) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.680 : Fin n) (x._@.Mathlib.Order.Hom.Basic._hyg.682 : Fin n) => LE.le.{0} (Fin n) (instLEFin n) x._@.Mathlib.Order.Hom.Basic._hyg.680 x._@.Mathlib.Order.Hom.Basic._hyg.682) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.695 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (x._@.Mathlib.Order.Hom.Basic._hyg.697 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (instLEFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) x._@.Mathlib.Order.Hom.Basic._hyg.695 x._@.Mathlib.Order.Hom.Basic._hyg.697) (Fin.succEmbedding n)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))
Case conversion may be inaccurate. Consider using '#align fin.Ioi_zero_eq_map Fin.Ioi_zero_eq_mapₓ'. -/
@[simp]
theorem Ioi_zero_eq_map : Ioi (0 : Fin n.succ) = univ.map (Fin.succEmbedding _).toEmbedding :=
  by
  ext i
  simp only [mem_Ioi, mem_map, mem_univ, Function.Embedding.coeFn_mk, exists_true_left]
  constructor
  · refine' cases _ _ i
    · rintro ⟨⟨⟩⟩
    · intro j _
      exact ⟨j, rfl⟩
  · rintro ⟨i, _, rfl⟩
    exact succ_pos _
#align fin.Ioi_zero_eq_map Fin.Ioi_zero_eq_map

/- warning: fin.Iio_last_eq_map -> Fin.Iio_last_eq_map is a dubious translation:
lean 3 declaration is
  forall {n : Nat}, Eq.{1} (Finset.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Finset.Iio.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (PartialOrder.toPreorder.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.partialOrder (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Fin.locallyFiniteOrderBot (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.last n)) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Fin.castSucc n)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))
but is expected to have type
  forall {n : Nat}, Eq.{1} (Finset.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (Finset.Iio.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (PartialOrder.toPreorder.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Fin.instPartialOrderFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (instLocallyFiniteOrderBotFinToPreorderInstPartialOrderFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Fin.last n)) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.680 : Fin n) (x._@.Mathlib.Order.Hom.Basic._hyg.682 : Fin n) => LE.le.{0} (Fin n) (instLEFin n) x._@.Mathlib.Order.Hom.Basic._hyg.680 x._@.Mathlib.Order.Hom.Basic._hyg.682) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.695 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (x._@.Mathlib.Order.Hom.Basic._hyg.697 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (instLEFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) x._@.Mathlib.Order.Hom.Basic._hyg.695 x._@.Mathlib.Order.Hom.Basic._hyg.697) (Fin.castSucc n)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))
Case conversion may be inaccurate. Consider using '#align fin.Iio_last_eq_map Fin.Iio_last_eq_mapₓ'. -/
@[simp]
theorem Iio_last_eq_map : Iio (Fin.last n) = Finset.univ.map Fin.castSucc.toEmbedding :=
  by
  apply Finset.map_injective Fin.valEmbedding
  rw [Finset.map_map, Fin.map_valEmbedding_Iio, Fin.val_last]
  exact map_subtype_embedding_univ.symm
#align fin.Iio_last_eq_map Fin.Iio_last_eq_map

/- warning: fin.Ioi_succ -> Fin.Ioi_succ is a dubious translation:
lean 3 declaration is
  forall {n : Nat} (i : Fin n), Eq.{1} (Finset.{0} (Fin (Nat.succ n))) (Finset.Ioi.{0} (Fin (Nat.succ n)) (PartialOrder.toPreorder.{0} (Fin (Nat.succ n)) (Fin.partialOrder (Nat.succ n))) (Fin.locallyFiniteOrderTop (Nat.succ n)) (Fin.succ n i)) (Finset.map.{0, 0} (Fin n) (Fin (Nat.succ n)) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Preorder.toLE.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (PartialOrder.toPreorder.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.partialOrder (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))))) (Fin.succEmbedding n)) (Finset.Ioi.{0} (Fin n) (PartialOrder.toPreorder.{0} (Fin n) (Fin.partialOrder n)) (Fin.locallyFiniteOrderTop n) i))
but is expected to have type
  forall {n : Nat} (i : Fin n), Eq.{1} (Finset.{0} (Fin (Nat.succ n))) (Finset.Ioi.{0} (Fin (Nat.succ n)) (PartialOrder.toPreorder.{0} (Fin (Nat.succ n)) (Fin.instPartialOrderFin (Nat.succ n))) (instForAllNatLocallyFiniteOrderTopFinToPreorderInstPartialOrderFin (Nat.succ n)) (Fin.succ n i)) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.680 : Fin n) (x._@.Mathlib.Order.Hom.Basic._hyg.682 : Fin n) => LE.le.{0} (Fin n) (instLEFin n) x._@.Mathlib.Order.Hom.Basic._hyg.680 x._@.Mathlib.Order.Hom.Basic._hyg.682) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.695 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (x._@.Mathlib.Order.Hom.Basic._hyg.697 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (instLEFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) x._@.Mathlib.Order.Hom.Basic._hyg.695 x._@.Mathlib.Order.Hom.Basic._hyg.697) (Fin.succEmbedding n)) (Finset.Ioi.{0} (Fin n) (PartialOrder.toPreorder.{0} (Fin n) (Fin.instPartialOrderFin n)) (instForAllNatLocallyFiniteOrderTopFinToPreorderInstPartialOrderFin n) i))
Case conversion may be inaccurate. Consider using '#align fin.Ioi_succ Fin.Ioi_succₓ'. -/
@[simp]
theorem Ioi_succ (i : Fin n) : Ioi i.succ = (Ioi i).map (Fin.succEmbedding _).toEmbedding :=
  by
  ext i
  simp only [mem_filter, mem_Ioi, mem_map, mem_univ, true_and_iff, Function.Embedding.coeFn_mk,
    exists_true_left]
  constructor
  · refine' cases _ _ i
    · rintro ⟨⟨⟩⟩
    · intro i hi
      refine' ⟨i, succ_lt_succ_iff.mp hi, rfl⟩
  · rintro ⟨i, hi, rfl⟩
    simpa
#align fin.Ioi_succ Fin.Ioi_succ

/- warning: fin.Iio_cast_succ -> Fin.Iio_castSucc is a dubious translation:
lean 3 declaration is
  forall {n : Nat} (i : Fin n), Eq.{1} (Finset.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Finset.Iio.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (PartialOrder.toPreorder.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.partialOrder (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Fin.locallyFiniteOrderBot (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (coeFn.{1, 1} (OrderEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.hasLe n) (Fin.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (fun (_x : RelEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))))) => (Fin n) -> (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (RelEmbedding.hasCoeToFun.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))))) (Fin.castSucc n) i)) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (LE.le.{0} (Fin n) (Fin.hasLe n)) (LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (Fin.castSucc n)) (Finset.Iio.{0} (Fin n) (PartialOrder.toPreorder.{0} (Fin n) (Fin.partialOrder n)) (Fin.locallyFiniteOrderBot n) i))
but is expected to have type
  forall {n : Nat} (i : Fin n), Eq.{1} (Finset.{0} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : Fin n) => Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) i)) (Finset.Iio.{0} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : Fin n) => Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) i) (PartialOrder.toPreorder.{0} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : Fin n) => Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) i) (Fin.instPartialOrderFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (instLocallyFiniteOrderBotFinToPreorderInstPartialOrderFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (FunLike.coe.{1, 1, 1} (Function.Embedding.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (Fin n) (fun (_x : Fin n) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : Fin n) => Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) _x) (EmbeddingLike.toFunLike.{1, 1, 1} (Function.Embedding.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Function.instEmbeddingLikeEmbedding.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.680 : Fin n) (x._@.Mathlib.Order.Hom.Basic._hyg.682 : Fin n) => LE.le.{0} (Fin n) (instLEFin n) x._@.Mathlib.Order.Hom.Basic._hyg.680 x._@.Mathlib.Order.Hom.Basic._hyg.682) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.695 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (x._@.Mathlib.Order.Hom.Basic._hyg.697 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (instLEFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) x._@.Mathlib.Order.Hom.Basic._hyg.695 x._@.Mathlib.Order.Hom.Basic._hyg.697) (Fin.castSucc n)) i)) (Finset.map.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (RelEmbedding.toEmbedding.{0, 0} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.680 : Fin n) (x._@.Mathlib.Order.Hom.Basic._hyg.682 : Fin n) => LE.le.{0} (Fin n) (instLEFin n) x._@.Mathlib.Order.Hom.Basic._hyg.680 x._@.Mathlib.Order.Hom.Basic._hyg.682) (fun (x._@.Mathlib.Order.Hom.Basic._hyg.695 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (x._@.Mathlib.Order.Hom.Basic._hyg.697 : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => LE.le.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (instLEFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) x._@.Mathlib.Order.Hom.Basic._hyg.695 x._@.Mathlib.Order.Hom.Basic._hyg.697) (Fin.castSucc n)) (Finset.Iio.{0} (Fin n) (PartialOrder.toPreorder.{0} (Fin n) (Fin.instPartialOrderFin n)) (instLocallyFiniteOrderBotFinToPreorderInstPartialOrderFin n) i))
Case conversion may be inaccurate. Consider using '#align fin.Iio_cast_succ Fin.Iio_castSuccₓ'. -/
@[simp]
theorem Iio_castSucc (i : Fin n) : Iio (castSucc i) = (Iio i).map Fin.castSucc.toEmbedding :=
  by
  apply Finset.map_injective Fin.valEmbedding
  rw [Finset.map_map, Fin.map_valEmbedding_Iio]
  exact (Fin.map_valEmbedding_Iio i).symm
#align fin.Iio_cast_succ Fin.Iio_castSucc

/- warning: fin.card_filter_univ_succ' -> Fin.card_filter_univ_succ' is a dubious translation:
lean 3 declaration is
  forall {n : Nat} (p : (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) -> Prop) [_inst_1 : DecidablePred.{1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) p], Eq.{1} Nat (Finset.card.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Finset.filter.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) p (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) => _inst_1 a) (Finset.univ.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.fintype (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (ite.{1} Nat (p (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) (_inst_1 (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) Prop p (Fin.succ n)) (fun (a : Fin n) => _inst_1 (Fin.succ n a)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))))
but is expected to have type
  forall {n : Nat} (p : (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) -> Prop) [_inst_1 : DecidablePred.{1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p], Eq.{1} Nat (Finset.card.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Finset.filter.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => _inst_1 a) (Finset.univ.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Fin.fintype (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (ite.{1} Nat (p (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (_inst_1 (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) Prop p (Fin.succ n)) (fun (a : Fin n) => instDecidablePredCompProp.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p (Fin.succ n) (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => _inst_1 a) a) (Finset.univ.{0} (Fin n) (Fin.fintype n)))))
Case conversion may be inaccurate. Consider using '#align fin.card_filter_univ_succ' Fin.card_filter_univ_succ'ₓ'. -/
theorem card_filter_univ_succ' (p : Fin (n + 1) → Prop) [DecidablePred p] :
    (univ.filterₓ p).card = ite (p 0) 1 0 + (univ.filterₓ (p ∘ Fin.succ)).card :=
  by
  rw [Fin.univ_succ, filter_cons, card_disj_union, filter_map, card_map]
  split_ifs <;> simp
#align fin.card_filter_univ_succ' Fin.card_filter_univ_succ'

/- warning: fin.card_filter_univ_succ -> Fin.card_filter_univ_succ is a dubious translation:
lean 3 declaration is
  forall {n : Nat} (p : (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) -> Prop) [_inst_1 : DecidablePred.{1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) p], Eq.{1} Nat (Finset.card.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Finset.filter.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) p (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) => _inst_1 a) (Finset.univ.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (Fin.fintype (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))))) (ite.{1} Nat (p (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) (_inst_1 (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (OfNat.mk.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) 0 (Zero.zero.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne))) (Fin.hasZeroOfNeZero (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (One.one.{0} Nat Nat.hasOne)) (NeZero.succ n)))))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) Prop p (Fin.succ n)) (fun (a : Fin n) => _inst_1 (Fin.succ n a)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) Prop p (Fin.succ n)) (fun (a : Fin n) => _inst_1 (Fin.succ n a)) (Finset.univ.{0} (Fin n) (Fin.fintype n)))))
but is expected to have type
  forall {n : Nat} (p : (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) -> Prop) [_inst_1 : DecidablePred.{1} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p], Eq.{1} Nat (Finset.card.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Finset.filter.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => _inst_1 a) (Finset.univ.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (Fin.fintype (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))))) (ite.{1} Nat (p (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (_inst_1 (OfNat.ofNat.{0} (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) 0 (Fin.instOfNatFin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) 0 (NeZero.succ n)))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) Prop p (Fin.succ n)) (fun (a : Fin n) => instDecidablePredCompProp.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p (Fin.succ n) (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => _inst_1 a) a) (Finset.univ.{0} (Fin n) (Fin.fintype n)))) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) (Finset.card.{0} (Fin n) (Finset.filter.{0} (Fin n) (Function.comp.{1, 1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) Prop p (Fin.succ n)) (fun (a : Fin n) => instDecidablePredCompProp.{1, 1} (Fin n) (Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p (Fin.succ n) (fun (a : Fin (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) => _inst_1 a) a) (Finset.univ.{0} (Fin n) (Fin.fintype n)))))
Case conversion may be inaccurate. Consider using '#align fin.card_filter_univ_succ Fin.card_filter_univ_succₓ'. -/
theorem card_filter_univ_succ (p : Fin (n + 1) → Prop) [DecidablePred p] :
    (univ.filterₓ p).card =
      if p 0 then (univ.filterₓ (p ∘ Fin.succ)).card + 1 else (univ.filterₓ (p ∘ Fin.succ)).card :=
  (card_filter_univ_succ' p).trans (by split_ifs <;> simp [add_comm 1])
#align fin.card_filter_univ_succ Fin.card_filter_univ_succ

#print Fin.card_filter_univ_eq_vector_get_eq_count /-
theorem card_filter_univ_eq_vector_get_eq_count [DecidableEq α] (a : α) (v : Vector α n) :
    (univ.filterₓ fun i => a = v.get? i).card = v.toList.count a :=
  by
  induction' v using Vector.inductionOn with n x xs hxs
  · simp
  ·
    simp_rw [card_filter_univ_succ', Vector.get_cons_zero, Vector.toList_cons, Function.comp,
      Vector.get_cons_succ, hxs, List.count_cons', add_comm (ite (a = x) 1 0)]
#align fin.card_filter_univ_eq_vector_nth_eq_count Fin.card_filter_univ_eq_vector_get_eq_count
-/

end Fin


import defs.var
import util.option
import util.set

theorem lookup_uniq {t: Type} [decidable_eq t] {Γ: cx t} {x: var} {v v': t}:
  cx.lookup Γ x v ->
  cx.lookup Γ x v' ->
  v = v' :=
begin
  simp [cx.lookup],
  intros la lb,
  by_cases v = v',
  exact h,
  let a := cx_elem.mk x v,
  let b := cx_elem.mk x v',
  let a_ne_b: cx_elem.mk x v ≠ cx_elem.mk x v' := begin
    intro h1,
    simp at h1,
    exact h h1,
  end,
  let hm := Γ.nodupkeys a b la lb a_ne_b,
  simp [ne_var] at hm,
  exfalso,
  exact hm,
end

theorem lookup_insert {t: Type} (Γ: cx t) (x: var) (v: t):
  cx.lookup (cx.insert Γ x v) x v :=
begin
  simp [cx.insert],
  simp [cx.lookup],
  left,
  exact iff.elim_right mem_singleton_iff rfl,
end

theorem useless_insert_ne {t: Type} {Γ: cx t} {x y: var} {vy v: t}:
  x ≠ y ->
  (cx.lookup (cx.insert Γ y vy) x v ↔ cx.lookup Γ x v) :=
begin
  intro h_ne,
  simp [cx.insert],
  simp [cx.lookup],
  split,
  intro h,
  cases h,
  cases iff.elim_left mem_singleton_iff h,
  exfalso,
  exact h_ne rfl,
  exact mem_pred h,
  intro h,
  right,
  split,
  exact h,
  simp,
  exact h_ne,
end

theorem entries_same {t: Type} {Γ Γ': cx t}:
  Γ.entries = Γ'.entries -> Γ = Γ' :=
begin
  cases Γ,
  cases Γ',
  simp,
  intro h,
  exact h,
end

theorem useless_insert_twice {t: Type}
  (Γ: cx t) (x: var) (v v': t):
  cx.insert (cx.insert Γ x v') x v = cx.insert Γ x v :=
begin
  simp [cx.insert],
  rw pred_distr_union,
  let hm: (cx_elem.mk x v').x = x := by simp,
  let hm': ¬ ¬ (cx_elem.mk x v').x = x := fun f, f hm,
  rw @singleton_not_pred (cx_elem t) (cx_elem.mk x v') (fun e, e.x ≠ x) hm',
  rw union_emp,
  rw double_pred,
end

theorem insert_comm' {t: Type}
  {Γ: cx t} {x y: var} {vx vy: t} {a: cx_elem t}:
  x ≠ y ->
  a ∈ (cx.insert (cx.insert Γ y vy) x vx).entries ->
  a ∈ (cx.insert (cx.insert Γ x vx) y vy).entries :=
begin
  intro h_ne,
  simp [cx.insert],
  intro h,
  cases h,
  right,
  split,
  left,
  exact h,
  rw iff.elim_left mem_singleton_iff h,
  simp,
  exact h_ne,
  cases h,
  cases h_left,
  left,
  exact h_left,
  right,
  split,
  right,
  split,
  exact mem_pred h_left,
  simp,
  exact h_right,
  simp,
  cases h_left,
  exact h_left_right,
end

theorem insert_comm {t: Type}
  (Γ: cx t) (x y: var) (vx vy: t) (h: x ≠ y):
  cx.insert (cx.insert Γ y vy) x vx =
  cx.insert (cx.insert Γ x vx) y vy :=
entries_same (set_ext begin
  intro a,
  split,
  exact insert_comm' h,
  let h' := fun x, h (symm x),
  exact insert_comm' h',
end)


/-
Copyright (c) 2021 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne
-/

import measure_theory.measure_space

/-!
# Sequence of measurable functions associated to a sequence of a.e.-measurable functions

We define here tools to prove statements about limits (infi, supr...) of sequences of
`ae_measurable` functions.
Given a sequence of a.e.-measurable functions `f : ι → α → β` with hypothesis
`hf : ∀ i, ae_measurable (f i) μ`, and a pointwise property `p : α → (ι → β) → Prop` such that we
have `hp : ∀ᵐ x ∂μ, p x (λ n, f n x)`, we define a sequence of measurable functions `ae_seq hf p`
and a measurable set `ae_seq_set hf p`, such that
* `μ (ae_seq_set hf p)ᶜ = 0`
* `x ∈ ae_seq_set hf p → ∀ i : ι, ae_seq hf hp i x = f i x`
* `x ∈ ae_seq_set hf p → p x (λ n, f n x)`
-/

open measure_theory
open_locale classical

variables {α β γ ι : Type*} [measurable_space α] [measurable_space β]
  {f : ι → α → β} {μ : measure α} {p : α → (ι → β) → Prop}

/-- If we have the additional hypothesis `∀ᵐ x ∂μ, p x (λ n, f n x)`, this is a measurable set
whose complement has measure 0 such that for all `x ∈ ae_seq_set`, `f i x` is equal to
`(hf i).mk (f i) x` for all `i` and we have the pointwise property `p x (λ n, f n x)`. -/
def ae_seq_set (hf : ∀ i, ae_measurable (f i) μ) (p : α → (ι → β) → Prop) : set α :=
(to_measurable μ {x | (∀ i, f i x = (hf i).mk (f i) x) ∧ p x (λ n, f n x)}ᶜ)ᶜ

/-- A sequence of measurable functions that are equal to `f` and verify property `p` on the
measurable set `ae_seq_set hf p`. -/
noncomputable
def ae_seq (hf : ∀ i, ae_measurable (f i) μ) (p : α → (ι → β) → Prop) : ι → α → β :=
λ i x, ite (x ∈ ae_seq_set hf p) ((hf i).mk (f i) x) (⟨f i x⟩ : nonempty β).some

namespace ae_seq

section mem_ae_seq_set

lemma mk_eq_fun_of_mem_ae_seq_set (hf : ∀ i, ae_measurable (f i) μ) {x : α}
  (hx : x ∈ ae_seq_set hf p) (i : ι) :
  (hf i).mk (f i) x = f i x :=
begin
  have h_ss : ae_seq_set hf p ⊆ {x | ∀ i, f i x = (hf i).mk (f i) x},
  { rw [ae_seq_set, ←compl_compl {x | ∀ i, f i x = (hf i).mk (f i) x}, set.compl_subset_compl],
    refine set.subset.trans (set.compl_subset_compl.mpr (λ x h, _)) (subset_to_measurable _ _),
    exact h.1, },
  exact (h_ss hx i).symm,
end

lemma ae_seq_eq_mk_of_mem_ae_seq_set (hf : ∀ i, ae_measurable (f i) μ) {x : α}
  (hx : x ∈ ae_seq_set hf p) (i : ι) :
  ae_seq hf p i x = (hf i).mk (f i) x :=
by simp only [ae_seq, hx, if_true]

lemma ae_seq_eq_fun_of_mem_ae_seq_set (hf : ∀ i, ae_measurable (f i) μ) {x : α}
  (hx : x ∈ ae_seq_set hf p) (i : ι) :
  ae_seq hf p i x = f i x :=
by simp only [ae_seq_eq_mk_of_mem_ae_seq_set hf hx i, mk_eq_fun_of_mem_ae_seq_set hf hx i]

lemma prop_of_mem_ae_seq_set (hf : ∀ i, ae_measurable (f i) μ)
  {x : α} (hx : x ∈ ae_seq_set hf p) :
  p x (λ n, ae_seq hf p n x) :=
begin
  simp only [ae_seq, hx, if_true],
  rw funext (λ n, mk_eq_fun_of_mem_ae_seq_set hf hx n),
  have h_ss : ae_seq_set hf p ⊆ {x | p x (λ n, f n x)},
  { rw [←compl_compl {x | p x (λ n, f n x)}, ae_seq_set, set.compl_subset_compl],
    refine set.subset.trans (set.compl_subset_compl.mpr _) (subset_to_measurable _ _),
    exact λ x hx, hx.2, },
  have hx' := set.mem_of_subset_of_mem h_ss hx,
  exact hx',
end

lemma fun_prop_of_mem_ae_seq_set (hf : ∀ i, ae_measurable (f i) μ)
  {x : α} (hx : x ∈ ae_seq_set hf p) :
  p x (λ n, f n x) :=
begin
  have h_eq : (λ n, f n x) = λ n, ae_seq hf p n x,
    from funext (λ n, (ae_seq_eq_fun_of_mem_ae_seq_set hf hx n).symm),
  rw h_eq,
  exact prop_of_mem_ae_seq_set hf hx,
end

end mem_ae_seq_set

lemma ae_seq_set_measurable_set {hf : ∀ i, ae_measurable (f i) μ} :
  measurable_set (ae_seq_set hf p) :=
(measurable_set_to_measurable _ _).compl

lemma measurable (hf : ∀ i, ae_measurable (f i) μ) (p : α → (ι → β) → Prop)
  (i : ι) :
  measurable (ae_seq hf p i) :=
begin
  refine measurable.ite ae_seq_set_measurable_set (hf i).measurable_mk _,
  by_cases hα : nonempty α,
  { exact @measurable_const _ _ _ _ (⟨f i hα.some⟩ : nonempty β).some },
  { exact measurable_of_not_nonempty hα _ }
end

lemma measure_compl_ae_seq_set_eq_zero [encodable ι] (hf : ∀ i, ae_measurable (f i) μ)
  (hp : ∀ᵐ x ∂μ, p x (λ n, f n x)) :
  μ (ae_seq_set hf p)ᶜ = 0 :=
begin
  rw [ae_seq_set, compl_compl, measure_to_measurable],
  have hf_eq := λ i, (hf i).ae_eq_mk,
  simp_rw [filter.eventually_eq, ←ae_all_iff] at hf_eq,
  exact filter.eventually.and hf_eq hp,
end

lemma ae_seq_eq_mk_ae [encodable ι] (hf : ∀ i, ae_measurable (f i) μ)
  (hp : ∀ᵐ x ∂μ, p x (λ n, f n x)) :
  ∀ᵐ (a : α) ∂μ, ∀ (i : ι), ae_seq hf p i a = (hf i).mk (f i) a :=
begin
  have h_ss : ae_seq_set hf p ⊆ {a : α | ∀ i, ae_seq hf p i a = (hf i).mk (f i) a},
    from λ x hx i, by simp only [ae_seq, hx, if_true],
  exact le_antisymm (le_trans (measure_mono (set.compl_subset_compl.mpr h_ss))
    (le_of_eq (measure_compl_ae_seq_set_eq_zero hf hp))) (zero_le _),
end

lemma ae_seq_eq_fun_ae [encodable ι] (hf : ∀ i, ae_measurable (f i) μ)
  (hp : ∀ᵐ x ∂μ, p x (λ n, f n x)) :
  ∀ᵐ (a : α) ∂μ, ∀ (i : ι), ae_seq hf p i a = f i a :=
begin
  have h_ss : {a : α | ¬∀ (i : ι), ae_seq hf p i a = f i a} ⊆ (ae_seq_set hf p)ᶜ,
    from λ x, mt (λ hx i, (ae_seq_eq_fun_of_mem_ae_seq_set hf hx i)),
  exact measure_mono_null h_ss (measure_compl_ae_seq_set_eq_zero hf hp),
end

lemma ae_seq_n_eq_fun_n_ae [encodable ι] (hf : ∀ i, ae_measurable (f i) μ)
  (hp : ∀ᵐ x ∂μ, p x (λ n, f n x)) (n : ι) :
  ae_seq hf p n =ᵐ[μ] f n:=
ae_all_iff.mp (ae_seq_eq_fun_ae hf hp) n

lemma supr [complete_lattice β] [encodable ι]
  (hf : ∀ i, ae_measurable (f i) μ) (hp : ∀ᵐ x ∂μ, p x (λ n, f n x)) :
  (⨆ n, ae_seq hf p n) =ᵐ[μ] ⨆ n, f n :=
begin
  simp_rw [filter.eventually_eq, ae_iff, supr_apply],
  have h_ss : ae_seq_set hf p ⊆ {a : α | (⨆ (i : ι), ae_seq hf p i a) = ⨆ (i : ι), f i a},
  { intros x hx,
    congr,
    exact funext (λ i, ae_seq_eq_fun_of_mem_ae_seq_set hf hx i), },
  exact measure_mono_null (set.compl_subset_compl.mpr h_ss)
    (measure_compl_ae_seq_set_eq_zero hf hp),
end

end ae_seq


--

set_option tactic.hygienic false in
theorem ex1 {a p q r : Prop} : p → (p → q) → (q → r) → r := by
intro _ h1 h2;
apply h2;
apply h1;
exact a_1 -- Bad practice, using name generated by `intro`.

theorem ex2 {a p q r : Prop} : p → (p → q) → (q → r) → r := by
intro _ h1 h2;
apply h2;
apply h1;
exact a_1 -- error "unknown identifier"

theorem ex3 {a p q r : Prop} : p → (p → q) → (q → r) → r := by
intro _ h1 h2;
apply h2;
apply h1;
assumption

example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by
  cases h₂;
  { apply h₁; exact h }; -- error "unknown identifier"
  exact h

set_option tactic.hygienic false in
example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by
cases h₂;
{ apply h₁; exact h }; -- hygiene is disabled
exact h

-- Hygienic versions
example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by
cases h₂ with
| inl h => apply h₁; exact h
| inr h => exact h

example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by
cases h₂;
{ apply h₁; assumption };
assumption

example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by
match h₂ with
| Or.inl _ => apply h₁; assumption
| Or.inr h => exact h

example {p q : Prop} (h₁ : p → q) (h₂ : p ∨ q) : q := by unhygienic
  cases h₂
  { apply h₁; exact h } -- hygiene is disabled
  exact h


import .prop data.buffer.parser

open parser

namespace prop

namespace parser

class has_vars (α : Type) :=
(var : string → α)

instance : has_vars symb :=
⟨symb.atom⟩

variables {α : Type} [is_symb α] [has_vars α]

-- whitespace
def Ws : parser unit :=
many' $ one_of' " \t\x0d\n".to_list

-- token
def tok (s : string) := str s >> Ws

/-
-- a variable: a, ..., z, A, ..., Z
def Var : parser nat := do
c ← sat (λ x, _root_.true),
Ws,
let n := c.to_nat in
if 65 ≤ n ∧ n ≤ 122 then
  return n
else
  parser.fail "not a letter"
-/

def is_letter (c : char) :=
let n := c.to_nat in 65 ≤ n ∧ n ≤ 122

instance : decidable_pred is_letter := by { delta is_letter, apply_instance }

-- a variable: a, ..., z, A, ..., Z
def Var : parser (exp α) := do
c ← sat is_letter,
Ws,
return (@exp.cst _ $ has_vars.var _ c.to_string)

-- an atom
def Atom : parser (exp α) :=
(tok "⊤" >> Ws >> return prop.true) <|>
(tok "⊥" >> Ws >> return prop.false) <|>
Var
--(do n ← Var, pure (exp.fvr α n))

section formula
parameters {β : Type} [is_symb β] [has_vars β] (Form : parser (exp β))

def ParenForm : parser (exp β) :=
tok "(" *> Form <* tok ")" <* Ws

def Form0 : parser (exp β) :=
(fix $ λ F, Atom <|> ParenForm <|> (do tok "¬", f ← F, pure (prop.not f))) <* Ws

def Form1 : parser (exp β) :=
fix $ λ F, do
  l ← Form0,
  (do tok "∧",
      r ← F,
      pure (prop.and l r)) <|>
    pure l

def Form2 : parser (exp β) :=
fix $ λ F, do
  l ← Form1,
  (do tok "∨",
      r ← F,
      pure (prop.or l r)) <|>
    pure l

def Form3 : parser (exp β) :=
fix $ λ F, do
  l ← Form2,
  (do tok "→",
      r ← F,
      pure (prop.imp l r)) <|>
    pure l

end formula

def Form : parser (exp α) := do
fix $ λ F, Form3 F

def Sequent : parser (seq  α) := do
l ← sep_by (tok ",") Form,
tok "⊢",
r ← sep_by (tok ",") Form,
return (l, r)

/-
def parse_prop_to_str (s : string) : string :=
match parser.run (Form : parser (exp symb)) s.to_char_buffer with
| (sum.inl error)  := error
| (sum.inr result) := fml2str result
end

def ex := parser.run (Form : parser (exp symb)) "(p ∧ ¬ q) → r ∨ s".to_char_buffer

#eval ex

def ex' := parse_prop_to_str "(p ∧ ¬ q) → r ∨ s"

#eval ex'
-/

section
open lean
open lean.parser
open interactive

/-
meta def mk_thm (s: string) : tactic unit :=
let S := (Sequent : parser (seq symb) in
match parser.run S s.to_char_buffer with
| (sum.inl error)  := do exact `(_root_.false)
| (sum.inr sq) := do  exact `(thm sq)
end

def thm_type (s : string) := Prop

meta def mk_thm' : tactic unit :=
do `(thm_type %%s) ← target | failed,
   ss ← eval_expr string s,
   mk_thm ss

notation `!seq` s := (by mk_thm' : thm_type s)

-/

-- I don't know whether this is any better than the other method.
reserve prefix `!seq `:100
@[user_notation]
meta def sequent_macro (_ : parse $ tk "!seq") (s : string) : lean.parser pexpr :=
let S := (Sequent : _root_.parser (seq symb)) in
match parser.run S s.to_char_buffer with
| (sum.inl error)  := do pure ``(_root_.false)
| (sum.inr sq) := do let sq' := `(sq) in pure ``(thm %%sq')
end

end

/-
example : !seq "p, q ⊢ p" :=
begin
  apply thm.id
end

example : !seq "⊢ (p → q → r) → (p → q) → p → r" :=
begin
  showgoal,
  apply thm.impr,
  apply thm.impr,
  apply thm.impr,
  apply thm.rl 2,
  apply thm.impl,
  apply thm.rl 1,
  apply thm.id,
  apply thm.impl,
  apply thm.impl,
  apply thm.id,
  apply thm.id,
  apply thm.id,
  showgoal
end

example : !seq "p ∧ (q → ¬ p) ⊢ ¬ q" :=
begin
  apply thm.andl,
  showgoal
  sorry
end

theorem foo : !seq "p ∧ q ⊢ q ∧ p" :=
begin
  apply thm.andl,
  apply thm.andr,
  apply thm.rl 1,
  apply thm.id,
  apply thm.id,
  showgoal
end

-/

end parser

def pp_thm {l r : list (exp symb)} (p : thm (l, r)) : string :=
sqt2str fml2str l r

end prop


import combinatorics.simple_graph.coloring

--OUTPUT 1
theorem bipartite_iff_two_colorable {V : Type*} (G : simple_graph V) [fintype V] : (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) :=
begin
  have h1 : (G.colorable 2) → (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)), from sorry,
  have h2 : (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) → (G.colorable 2), from sorry,
  show (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)), from sorry,
end

--OUTPUT 2
theorem bipartite_iff_two_colorable {V : Type*} (G : simple_graph V) [fintype V] : (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) :=
begin
  split,
  {
    assume h1 : G.colorable 2,
    have h2 : ∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B), from sorry,
    show (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)), from h2,
  },
  {
    assume h1 : (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)),
    have h2 : G.colorable 2, from sorry,
    show G.colorable 2, from h2,
  }
end

--OUTPUT 3
theorem bipartite_iff_two_colorable {V : Type*} (G : simple_graph V) [fintype V] : (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) :=
begin
  have h1 : (G.colorable 2) → (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)), from sorry,
  have h2 : (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) → (G.colorable 2), from sorry,
  show (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)), from sorry,
end

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from sorry,
  have h2 : (A ∩ B) ⊆ A, from sorry,
  have h3 : (A ∩ B) ⊆ S, from sorry,
  show (A ∩ B) ∈  𝒫 S, from sorry,
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by sorry
  ... = x*(x+y) + y*(x+y) : by sorry
  ... = x*x + x*y + y*x + y*y : by sorry
  ... = x^2 + 2*x*y + y^2 : by sorry,
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from sorry,
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from sorry,

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from sorry,
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from sorry,

  have h5 : ∀ a : G, classical.some (h3 a) = (1 : G), from sorry,
  have h6 : ∀ a : G, classical.some (h4 a) = (1 : G), from sorry,

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (h7 : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a), from sorry,
      have h9 : ∀ a : G, e = classical.some (h4 a), from sorry,
      show e = (1 : G), from sorry,
    },
    sorry,
  }
end

/--`theorem`
Bipartite Graph is two colorable
Let $G$ be a graph. Then $G$ is 2-colorable if and only if $G$ is bipartite.
`proof`
Let $G$ be a 2-colorable graph, which means we can color every vertex either red or blue, and no edge will have both endpoints colored the same color. Let $A$ denote the subset of vertices colored red, and let $B$ denote the subset of vertices colored blue. Since all vertices of $A$ are red, there are no edges within $A$, and similarly for $B$. This implies that every edge has one endpoint in $A$ and the other in $B$, which means $G$ is bipartite.

Conversely, suppose $G$ is bipartite, that is, we can partition the vertices into two subsets $V_{1}, V_{2}$ every edge has one endpoint in $V_{1}$ and the other in $V_{2}$. Then coloring every vertex of $V_{1}$ red and every vertex of $V_{2}$ blue yields a valid coloring, so $G$ is 2-colorable.

QED

-/
theorem  bipartite_iff_two_colorable {V : Type*} (G : simple_graph V) [fintype V] : (G.colorable 2) ↔ (∃ (A B : Type*) (h : (A ⊕ B) = V), G ≤ cast (congr_arg _ h) (complete_bipartite_graph A B)) :=
FEW SHOT PROMPTS TO CODEX(END)-/


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov, Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.set.intervals.basic
import Mathlib.PostPort

universes u_1 u_2

namespace Mathlib

/-!
# Projection of a line onto a closed interval

Given a linearly ordered type `α`, in this file we define

* `set.proj_Icc (a b : α) (h : a ≤ b)` to be the map `α → [a, b]` sending `(-∞, a]` to `a`, `[b, ∞)`
  to `b`, and each point `x ∈ [a, b]` to itself;
* `set.Icc_extend {a b : α} (h : a ≤ b) (f : Icc a b → β)` to be the extension of `f` to `α` defined
  as `f ∘ proj_Icc a b h`.

We also prove some trivial properties of these maps.
-/

namespace set

/-- Projection of `α` to the closed interval `[a, b]`. -/
def proj_Icc {α : Type u_1} [linear_order α] (a : α) (b : α) (h : a ≤ b) (x : α) : ↥(Icc a b) :=
  { val := max a (min b x), property := sorry }

theorem proj_Icc_of_le_left {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) {x : α}
    (hx : x ≤ a) : proj_Icc a b h x = { val := a, property := iff.mpr left_mem_Icc h } :=
  sorry

@[simp] theorem proj_Icc_left {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    proj_Icc a b h a = { val := a, property := iff.mpr left_mem_Icc h } :=
  proj_Icc_of_le_left h le_rfl

theorem proj_Icc_of_right_le {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) {x : α}
    (hx : b ≤ x) : proj_Icc a b h x = { val := b, property := iff.mpr right_mem_Icc h } :=
  sorry

@[simp] theorem proj_Icc_right {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    proj_Icc a b h b = { val := b, property := iff.mpr right_mem_Icc h } :=
  proj_Icc_of_right_le h le_rfl

theorem proj_Icc_of_mem {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) {x : α}
    (hx : x ∈ Icc a b) : proj_Icc a b h x = { val := x, property := hx } :=
  sorry

@[simp] theorem proj_Icc_coe {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b)
    (x : ↥(Icc a b)) : proj_Icc a b h ↑x = x :=
  subtype.cases_on x fun (x_val : α) (x_property : x_val ∈ Icc a b) => proj_Icc_of_mem h x_property

theorem proj_Icc_surj_on {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    surj_on (proj_Icc a b h) (Icc a b) univ :=
  fun (x : ↥(Icc a b)) (_x : x ∈ univ) =>
    Exists.intro ↑x { left := subtype.property x, right := proj_Icc_coe h x }

theorem proj_Icc_surjective {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    function.surjective (proj_Icc a b h) :=
  fun (x : ↥(Icc a b)) => Exists.intro (↑x) (proj_Icc_coe h x)

@[simp] theorem range_proj_Icc {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    range (proj_Icc a b h) = univ :=
  function.surjective.range_eq (proj_Icc_surjective h)

theorem monotone_proj_Icc {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    monotone (proj_Icc a b h) :=
  fun (x y : α) (hxy : x ≤ y) => max_le_max le_rfl (min_le_min le_rfl hxy)

theorem strict_mono_incr_on_proj_Icc {α : Type u_1} [linear_order α] {a : α} {b : α} (h : a ≤ b) :
    strict_mono_incr_on (proj_Icc a b h) (Icc a b) :=
  sorry

/-- Extend a function `[a, b] → β` to a map `α → β`. -/
def Icc_extend {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α} (h : a ≤ b)
    (f : ↥(Icc a b) → β) : α → β :=
  f ∘ proj_Icc a b h

@[simp] theorem Icc_extend_range {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) (f : ↥(Icc a b) → β) : range (Icc_extend h f) = range f :=
  sorry

theorem Icc_extend_of_le_left {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) {x : α} (f : ↥(Icc a b) → β) (hx : x ≤ a) :
    Icc_extend h f x = f { val := a, property := iff.mpr left_mem_Icc h } :=
  congr_arg f (proj_Icc_of_le_left h hx)

@[simp] theorem Icc_extend_left {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) (f : ↥(Icc a b) → β) :
    Icc_extend h f a = f { val := a, property := iff.mpr left_mem_Icc h } :=
  Icc_extend_of_le_left h f le_rfl

theorem Icc_extend_of_right_le {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) {x : α} (f : ↥(Icc a b) → β) (hx : b ≤ x) :
    Icc_extend h f x = f { val := b, property := iff.mpr right_mem_Icc h } :=
  congr_arg f (proj_Icc_of_right_le h hx)

@[simp] theorem Icc_extend_right {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) (f : ↥(Icc a b) → β) :
    Icc_extend h f b = f { val := b, property := iff.mpr right_mem_Icc h } :=
  Icc_extend_of_right_le h f le_rfl

theorem Icc_extend_of_mem {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α} (h : a ≤ b)
    {x : α} (f : ↥(Icc a b) → β) (hx : x ∈ Icc a b) :
    Icc_extend h f x = f { val := x, property := hx } :=
  congr_arg f (proj_Icc_of_mem h hx)

@[simp] theorem Icc_extend_coe {α : Type u_1} {β : Type u_2} [linear_order α] {a : α} {b : α}
    (h : a ≤ b) (f : ↥(Icc a b) → β) (x : ↥(Icc a b)) : Icc_extend h f ↑x = f x :=
  congr_arg f (proj_Icc_coe h x)

end set

theorem monotone.Icc_extend {α : Type u_1} {β : Type u_2} [linear_order α] [preorder β] {a : α}
    {b : α} (h : a ≤ b) {f : ↥(set.Icc a b) → β} (hf : monotone f) :
    monotone (set.Icc_extend h f) :=
  monotone.comp hf (set.monotone_proj_Icc h)

theorem strict_mono.strict_mono_incr_on_Icc_extend {α : Type u_1} {β : Type u_2} [linear_order α]
    [preorder β] {a : α} {b : α} (h : a ≤ b) {f : ↥(set.Icc a b) → β} (hf : strict_mono f) :
    strict_mono_incr_on (set.Icc_extend h f) (set.Icc a b) :=
  strict_mono.comp_strict_mono_incr_on hf (set.strict_mono_incr_on_proj_Icc h)

end Mathlib