import Mathlib.Data.Real.Basic
import Mathlib.Tactic.FieldSimp
import Mathlib.Tactic.LibrarySearch

theorem dva_krat (n : Nat) : 2 * n = n + n := two_mul n

theorem tri_krat (n : Nat) : 3 * n = n + n + n := by
  convert_to 3 * n = 2 * n + n
  exact symm (dva_krat n)
  exact Nat.succ_mul 2 n

theorem tri_krat' (n : Nat) : 3 * n = n + n + n := by ring

theorem soucet_na_druhou (x y : ℤ) : (x + y) ^ 2 = x^2 + 2*x*y + y^2 := by ring

theorem rozdil_na_treti (x y : ℚ) : (x - y) ^ 3 = x^3 - 3*x^2*y + 3*x*y^2 - y^3 := by ring

theorem rozdil_patych_mocnin (x y : ℝ) : x^5 - y^5 = (x - y) * (x^4 + x^3*y + x^2*y^2 + x*y^3 + y^4) := by ring

example (x y : ℝ) : (x + y) ^ 2 - (x - y) ^ 2 = 4 * x * y := by ring

example (n : Nat) : 2 ^ (n+3) = 8 * 2^n := by ring

example (n : Nat) (n_je_pet : n = 5) : n - 1 = 4 := by
  rw [n_je_pet]

example (n : Nat) (n_je_pet : 5 = n) : n - 1 = 4 := by
  rw [symm n_je_pet]

example (n : Nat) (n_je_pet : 5 = n) : n - 1 = 4 := by
  rw [←n_je_pet]

example (a b c : ℝ) (a_je_dva : a = 2) (b_je_tri : b = 3) (c_je_pet : c = 5) : a + b = c := by
  rw [a_je_dva, b_je_tri, c_je_pet]
  ring

example (x : ℝ) (xnn : x ≠ 0) : x^2 / x = x := by
  field_simp
  ring

theorem plus_prevracena (x : ℝ) (xnn : x ≠ 0) : x + 1/x = (x^2 + 1) / x := by
  field_simp
  ring

example (x y z : ℝ) (xnn : x ≠ 0) : x*y*z + 3*y*z*x - 2*z*x*y = y*x*z + x^2*z*y/x := by
  field_simp
  ring

example (x y z : ℝ) (xy : x ≤ y) (yz : y ≤ z) : x ≤ z := Trans.simple xy yz

example (x y z : ℝ) (xy : x < y) (yz : y < z) : x < z := Trans.simple xy yz

example (x y z : ℝ) (xy : x < y) (yz : y ≤ z) : x < z := instTransLtToLTLeToLE.proof_1 xy yz

example (x y z : ℝ) (xy : x ≤ y) (yz : y < z) : x < z := xy.trans_lt yz

example (a b c d : ℝ) (abcd : a + b + c ≤ 2 * d) (ab : a ≤ b) (ac : 2 * a ≤ c) : 2 * a ≤ d := by linarith

example (x y : ℝ) (xy : x ≤ y) : x ≤ y + y*y := by nlinarith

example (x y : ℝ) (x_zaporne : x < 0) (y_zaporne : y < 0) : x * 7 * y > 0 := by nlinarith

example (x y : ℝ) : x*x - 2*x*y + y*y ≥ 0 := by
  convert_to (x - y) ^ 2 ≥ 0
  ring
  nlinarith

example (x : ℝ) : 16*x^4 - 96*x^3 + 216*x^2 - 216*x + 81 ≥ 0 := by
  convert_to ((2*x - 3) ^ 2) ^ 2 ≥ 0
  ring
  nlinarith

example (x : ℝ) : 16*x^4 - 96*x^3 + 216*x^2 - 216*x + 100 ≥ 0 := by
  have pomocne : 16*x^4 - 96*x^3 + 216*x^2 - 216*x + 81 ≥ 0
  · convert_to ((2*x - 3) ^ 2) ^ 2 ≥ 0
    · ring
    nlinarith
  linarith

example (x : ℝ) : 16*x^4 - 96*x^3 + 216*x^2 - 216*x + 100 ≥ 0 := by
  have pomocne : 16*x^4 - 96*x^3 + 216*x^2 - 216*x + 81 ≥ 0
  convert_to ((2*x - 3) ^ 2) ^ 2 ≥ 0
  ring
  nlinarith
  linarith

example (x : ℝ) (xpos : x > 0) : x + 1/x ≥ 2 := by
  have : (x - 1) ^ 2 ≥ 0
  · exact pow_two_nonneg (x - 1)
  have : x^2 + 1 - 2*x ≥ 0
  · convert this
    ring
  have : x^2 + 1 ≥ 2*x
  · exact le_of_sub_nonneg this
  have : (x^2 + 1) / x ≥ (2*x) / x
  · have left_numerator_nneg : x^2 + 1 ≥ 0
    · nlinarith
    have wtf : x ≤ x
    · rfl
    exact div_le_div left_numerator_nneg this xpos wtf
  convert this
  · ring_nf
    congr
    convert_to x = x * x * x⁻¹
    ring
    simp
  · have : (2 : ℝ) = 2 * 1
    · ring
    have : 2 = 2 * (x / x)
    · convert this
      have : x ≠ 0
      · exact LT.lt.ne' xpos
      exact div_self this
    convert this
    field_simp

example (x : ℝ) (predpoklad : x ≠ -1) : (x^2 + x) / (2*x + 2) = x / 2 := by
  convert_to (x * (x + 1)) / ((x + 1) * 2) = x / 2
  · ring
  · ring
  convert_to x * ((x + 1) / ((x + 1) * 2)) = x / 2
  · field_simp
  have pokraceni : (x + 1) / ((x + 1) * 2) = 1 / 2
  · rw [←div_div, div_self]
    intro prospor
    apply predpoklad
    exact eq_neg_of_add_eq_zero_left prospor
  rw [pokraceni]
  rw [mul_div, mul_one]

example (x y : ℝ) (predpoklad : 3*x + y ≠ 0) : (3*x + y) ^ 5 / (3*x + y) ^ 4 = 3*x + y := by
  rw [pow_succ, ←mul_div, div_self, mul_one]
  exact pow_ne_zero 4 predpoklad


/-
Copyright (c) 2021 Yakov Pechersky All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky
-/
import data.equiv.basic
import tactic.norm_fin

/-!
# `norm_swap`

Evaluating `swap x y z` for numerals `x y z` that are `ℕ`, `ℤ`, or `ℚ`, via a `norm_num` plugin.
Terms are passed to `eval`, quickly failing if not of the form `swap x y z`.
The expressions for numerals `x y z` are converted to `nat`, and then compared.
Based on equality of these `nat`s, equality proofs are generated using either
`equiv.swap_apply_left`, `equiv.swap_apply_right`, or `swap_apply_of_ne_of_ne`.
-/

open equiv tactic expr

open norm_num

namespace norm_swap

/--
A `norm_num` plugin for normalizing `equiv.swap a b c`
where `a b c` are numerals of `ℕ`, `ℤ`, `ℚ` or `fin n`.

```
example : equiv.swap 1 2 1 = 2 := by norm_num
```
-/
@[norm_num] meta def eval : expr → tactic (expr × expr) := λ e, do
  (swapt, fun_ty, coe_fn_inst, fexpr, c) ← e.match_app_coe_fn
    <|> fail "did not get an app coe_fn expr",
  guard (fexpr.get_app_fn.const_name = ``equiv.swap) <|> fail "coe_fn not of equiv.swap",
  [α, deceq_inst, a, b] ← pure fexpr.get_app_args <|>
    fail "swap did not have exactly two args applied",
  na ← a.to_rat <|> (do (fa, _) ← norm_fin.eval_fin_num a, fa.to_rat),
  nb ← b.to_rat <|> (do (fb, _) ← norm_fin.eval_fin_num b, fb.to_rat),
  nc ← c.to_rat <|> (do (fc, _) ← norm_fin.eval_fin_num c, fc.to_rat),
  if nc = na then do
    p ← mk_mapp `equiv.swap_apply_left [α, deceq_inst, a, b],
    pure (b, p)
  else if nc = nb then do
    p ← mk_mapp `equiv.swap_apply_right [α, deceq_inst, a, b],
    pure (a, p)
  else do
    nic ← mk_instance_cache α,
    hca ← (prod.snd <$> prove_ne nic c a nc na) <|>
      (do (_, ff, p) ← norm_fin.prove_eq_ne_fin c a, pure p),
    hcb ← (prod.snd <$> prove_ne nic c b nc nb) <|>
      (do (_, ff, p) ← norm_fin.prove_eq_ne_fin c b, pure p),
    p ← mk_mapp `equiv.swap_apply_of_ne_of_ne [α, deceq_inst, a, b, c, hca, hcb],
    pure (c, p)

end norm_swap


/-
Copyright (c) 2021 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller
-/
import data.finset.sort
import data.list.fin_range
import data.prod.lex
import group_theory.perm.basic

/-!

# Sorting tuples by their values

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

Given an `n`-tuple `f : fin n → α` where `α` is ordered,
we may want to turn it into a sorted `n`-tuple.
This file provides an API for doing so, with the sorted `n`-tuple given by
`f ∘ tuple.sort f`.

## Main declarations

* `tuple.sort`: given `f : fin n → α`, produces a permutation on `fin n`
* `tuple.monotone_sort`: `f ∘ tuple.sort f` is `monotone`

-/

namespace tuple

variables {n : ℕ}
variables {α : Type*} [linear_order α]

/--
`graph f` produces the finset of pairs `(f i, i)`
equipped with the lexicographic order.
-/
def graph (f : fin n → α) : finset (α ×ₗ (fin n)) :=
finset.univ.image (λ i, (f i, i))

/--
Given `p : α ×ₗ (fin n) := (f i, i)` with `p ∈ graph f`,
`graph.proj p` is defined to be `f i`.
-/
def graph.proj {f : fin n → α} : graph f → α := λ p, p.1.1

@[simp] lemma graph.card (f : fin n → α) : (graph f).card = n :=
begin
  rw [graph, finset.card_image_of_injective],
  { exact finset.card_fin _ },
  { intros _ _,
    simp }
end

/--
`graph_equiv₁ f` is the natural equivalence between `fin n` and `graph f`,
mapping `i` to `(f i, i)`. -/
def graph_equiv₁ (f : fin n → α) : fin n ≃ graph f :=
{ to_fun := λ i, ⟨(f i, i), by simp [graph]⟩,
  inv_fun := λ p, p.1.2,
  left_inv := λ i, by simp,
  right_inv := λ ⟨⟨x, i⟩, h⟩, by simpa [graph] using h }

@[simp] lemma proj_equiv₁' (f : fin n → α) : graph.proj ∘ graph_equiv₁ f = f :=
rfl

/--
`graph_equiv₂ f` is an equivalence between `fin n` and `graph f` that respects the order.
-/
def graph_equiv₂ (f : fin n → α) : fin n ≃o graph f :=
finset.order_iso_of_fin _ (by simp)

/-- `sort f` is the permutation that orders `fin n` according to the order of the outputs of `f`. -/
def sort (f : fin n → α) : equiv.perm (fin n) :=
(graph_equiv₂ f).to_equiv.trans (graph_equiv₁ f).symm

lemma graph_equiv₂_apply (f : fin n → α) (i : fin n) :
  graph_equiv₂ f i = graph_equiv₁ f (sort f i) :=
((graph_equiv₁ f).apply_symm_apply _).symm

lemma self_comp_sort (f : fin n → α) : f ∘ sort f = graph.proj ∘ graph_equiv₂ f :=
show graph.proj ∘ ((graph_equiv₁ f) ∘ (graph_equiv₁ f).symm) ∘ (graph_equiv₂ f).to_equiv = _,
  by simp

lemma monotone_proj (f : fin n → α) : monotone (graph.proj : graph f → α) :=
begin
  rintro ⟨⟨x, i⟩, hx⟩ ⟨⟨y, j⟩, hy⟩ (_|h),
  { exact le_of_lt ‹_› },
  { simp [graph.proj] },
end

lemma monotone_sort (f : fin n → α) : monotone (f ∘ sort f) :=
begin
  rw [self_comp_sort],
  exact (monotone_proj f).comp (graph_equiv₂ f).monotone,
end

end tuple

namespace tuple

open list

variables {n : ℕ} {α : Type*}

/-- If two permutations of a tuple `f` are both monotone, then they are equal. -/
lemma unique_monotone [partial_order α] {f : fin n → α} {σ τ : equiv.perm (fin n)}
  (hfσ : monotone (f ∘ σ)) (hfτ : monotone (f ∘ τ)) : f ∘ σ = f ∘ τ :=
of_fn_injective $ eq_of_perm_of_sorted
  ((σ.of_fn_comp_perm f).trans (τ.of_fn_comp_perm f).symm) hfσ.of_fn_sorted hfτ.of_fn_sorted

variables [linear_order α] {f : fin n → α} {σ : equiv.perm (fin n)}

/-- A permutation `σ` equals `sort f` if and only if the map `i ↦ (f (σ i), σ i)` is
strictly monotone (w.r.t. the lexicographic ordering on the target). -/
lemma eq_sort_iff' : σ = sort f ↔ strict_mono (σ.trans $ graph_equiv₁ f) :=
begin
  split; intro h,
  { rw [h, sort, equiv.trans_assoc, equiv.symm_trans_self], exact (graph_equiv₂ f).strict_mono },
  { have := subsingleton.elim (graph_equiv₂ f) (h.order_iso_of_surjective _ $ equiv.surjective _),
    ext1, exact (graph_equiv₁ f).apply_eq_iff_eq_symm_apply.1 (fun_like.congr_fun this x).symm },
end

/-- A permutation `σ` equals `sort f` if and only if `f ∘ σ` is monotone and whenever `i < j`
and `f (σ i) = f (σ j)`, then `σ i < σ j`. This means that `sort f` is the lexicographically
smallest permutation `σ` such that `f ∘ σ` is monotone. -/
lemma eq_sort_iff : σ = sort f ↔ monotone (f ∘ σ) ∧ ∀ i j, i < j → f (σ i) = f (σ j) → σ i < σ j :=
begin
  rw eq_sort_iff',
  refine ⟨λ h, ⟨(monotone_proj f).comp h.monotone, λ i j hij hfij, _⟩, λ h i j hij, _⟩,
  { exact (((prod.lex.lt_iff _ _).1 $ h hij).resolve_left hfij.not_lt).2 },
  { obtain he|hl := (h.1 hij.le).eq_or_lt; apply (prod.lex.lt_iff _ _).2,
    exacts [or.inr ⟨he, h.2 i j hij he⟩, or.inl hl] },
end

/-- The permutation that sorts `f` is the identity if and only if `f` is monotone. -/
lemma sort_eq_refl_iff_monotone : sort f = equiv.refl _ ↔ monotone f :=
begin
  rw [eq_comm, eq_sort_iff, equiv.coe_refl, function.comp.right_id],
  simp only [id.def, and_iff_left_iff_imp],
  exact λ _ _ _ hij _, hij,
end

/-- A permutation of a tuple `f` is `f` sorted if and only if it is monotone. -/
lemma comp_sort_eq_comp_iff_monotone : f ∘ σ = f ∘ sort f ↔ monotone (f ∘ σ) :=
⟨λ h, h.symm ▸ monotone_sort f, λ h, unique_monotone h (monotone_sort f)⟩

/-- The sorted versions of a tuple `f` and of any permutation of `f` agree. -/
lemma comp_perm_comp_sort_eq_comp_sort : (f ∘ σ) ∘ (sort (f ∘ σ)) = f ∘ sort f :=
begin
  rw [function.comp.assoc, ← equiv.perm.coe_mul],
  exact unique_monotone (monotone_sort (f ∘ σ)) (monotone_sort f),
end

/-- If a permutation `f ∘ σ` of the tuple `f` is not the same as `f ∘ sort f`, then `f ∘ σ`
has a pair of strictly decreasing entries. -/
lemma antitone_pair_of_not_sorted' (h : f ∘ σ ≠ f ∘ sort f) :
  ∃ i j, i < j ∧ (f ∘ σ) j < (f ∘ σ) i :=
by { contrapose! h, exact comp_sort_eq_comp_iff_monotone.mpr (monotone_iff_forall_lt.mpr h) }

/-- If the tuple `f` is not the same as `f ∘ sort f`, then `f` has a pair of strictly decreasing
entries. -/
lemma antitone_pair_of_not_sorted (h : f ≠ f ∘ sort f) : ∃ i j, i < j ∧ f j < f i :=
antitone_pair_of_not_sorted' (id h : f ∘ equiv.refl _ ≠ _)

end tuple


import data.real.basic
import data.nat.prime
open nat

theorem GcdM : ∀ a b : nat, b ≤ a → ∃ g : nat, g ∣ a ∧ g ∣ b ∧ ∀ h : nat, h ∣ a ∧ h ∣ b → h ∣ g :=
begin
  intro a,

  -- As we will prove this by Strong Induction, we state the strong induction hypotheses
  have StrongInductionHyp : ∀ n : nat, (∀ a < n, ∀ b : nat, (b ≤ a → ∃ g : nat, g ∣ a ∧ g ∣ b ∧ ∀ h : nat, h ∣ a ∧ h ∣ b → h ∣ g)) →
                  (∀ b : nat, b ≤ n  → ∃ g : nat, g ∣ n ∧ g ∣ b ∧ ∀ h : nat, h ∣ n ∧ h ∣ b → h ∣ g) :=
  begin
    -- Proof of the strong induction hypothesis

    intro n,
    intro HypRec,
    intro b,
    intro bn,

    --We have to check the cases b = 0 separately, because n - b wouldn't be less than n in this case
    cases b with b,

    -- b = 0 case : just use n as g
    use n,
    split,
    use 1,
    simp,
    split,
    use 0,
    simp,
    intro h,
    intro hh,
    cases hh,
    exact hh_left,

    -- b > 0 case
    -- We define c = n - b.succ
    have ExistC := le.dest bn,
    cases ExistC with c K,

    -- Separation of b.succ <= c and c <= b.succ
    have BC := le_total b.succ c,
    cases BC,

    --Verifiying that c < n so that we can use HypRec
    have c_lt_n : c < n :=
    begin
      rw add_comm at K,
      rw ← add_one at K,
      rw add_comm b 1 at K,
      rw ← add_assoc at K,
      have w := Exists.intro b K,
      have z := le.intro K,
      exact z,
    end,

    -- HypRec gives us a g which is gcd(b.succ, c)
    have G := HypRec c c_lt_n b.succ BC,
    cases G with g G, -- extraction of g from its existence
    use g,

    -- Separation of the 3 conditions of gcd
    cases G with Gc temp,
    cases temp with Gbs Gmaxi,

    split,

    -- Verification that g ∣ n, using b.succ + c = n, knowing that g ∣ b.succ and g ∣ c
    cases Gc with r,
    rw Gc_h at K,
    cases Gbs with s,
    rw Gbs_h at K,
    rw ← mul_add at K,
    use (s + r),
    rw eq_comm at K,
    exact K,

    split,

    -- Verification that g ∣ b.succ, trivial
    exact Gbs,

    --Verification that g is the max possible.
    intro h,
    intro hdiv,
    cases hdiv with hdivn hdivmsucc,
    cases hdivn with k,
    rw hdivn_h at K,
    cases hdivmsucc with l,
    rw hdivmsucc_h at K,

    -- As we cannot use minus as we work in nat, we have to prove that l ≤ k. So we begin by proving h * l ≤ h * k
    --have hl_le_hk := (le_exist (h * l) (h * k)).2 (Exists.intro c K),
    have hl_le_hk := le.intro K,

    -- We will use that we can simplify h * l ≤ h * k if h ≠ 0
    cases h,
    -- For h = 0
    exfalso,
    rw zero_mul at hdivmsucc_h,
    have q := succ_ne_zero b,
    exact q hdivmsucc_h,
    -- For h > 0
    have temp := (mul_le_mul_left (ne_zero.pos (succ h))).1 hl_le_hk,
    have l_le_k := le.dest temp,
    -- Extraction of o = k - l
    cases l_le_k with o,
    rw eq_comm at l_le_k_h,
    rw l_le_k_h at K,
    rw mul_add at K,
    have q := add_left_cancel K,
    have hsdc : h.succ ∣ c := Exists.intro o q,
    have hsdms := Exists.intro l hdivmsucc_h,
    -- Use of recurrence property
    exact Gmaxi h.succ (and.intro hsdc hsdms),

    -- Second case : c <= b.succ. It's essentially the same as the first case, so I do not comment it.
    cases c with C,
    use n,
    split,
    use 1,
    simp,

    split,

    rw add_zero at K,
    rw K,
    rw add_zero at K,
    intro h,
    intro hn,
    cases hn,
    exact hn_left,
    rw add_succ at K,
    rw add_comm at K,
    rw ← add_succ at K,
    rw add_comm at K,
    have msltn := le.intro K,
    have maxi := HypRec b.succ msltn C.succ BC,
    cases maxi with g,
    use g,
    cases maxi_h,
    split,
    cases maxi_h_right with ab ac,
    rw succ_add at K,
    rw ← add_succ at K,
    cases ab with k,
    cases maxi_h_left with l,
    rw ab_h at K,
    rw maxi_h_left_h at K,
    rw ← mul_add at K,
    use (l + k),
    rw eq_comm at K,
    assumption,
    split,
    assumption,

    intro h,
    intro hnhms,
    cases hnhms,
    cases maxi_h_right with gcs mini,
    rw succ_add at K,
    rw ← add_succ at K,
    cases hnhms_left with r R,
    have hdms := hnhms_right,
    cases hnhms_right with u mshu,
    rw mshu at K,
    rw R at K,
    cases h with hp,
    exfalso,
    rw zero_mul at R,
    rw R at msltn,
    have k := zero_lt_succ b.succ,
    exact le_lt_antisymm msltn k,

    have temp := le.intro K,
    have ztz := (mul_le_mul_left (ne_zero.pos (succ hp))).1 temp,
    have urt := le.dest ztz,
    cases urt with U,
    rw eq_comm at urt_h,
    rw urt_h at K,
    rw mul_add at K,
    have qw := add_left_cancel K,
    have hpc : hp.succ ∣ C.succ := Exists.intro U qw,
    exact mini hp.succ (and.intro hdms hpc),
  end,

  -- We apply strong induction
  exact nat.strong_induction_on a StrongInductionHyp,
end


import data.real.basic

-- BEGIN
theorem aux {x y : ℝ} (h : x^2 + y^2 = 0) : x = 0 :=
begin
  have h' : x^2 = 0 := ((add_eq_zero_iff' (sq_nonneg x) (sq_nonneg y)).mp h).1,
  exact pow_eq_zero h',
end

example (x y : ℝ) : x^2 + y^2 = 0 ↔ x = 0 ∧ y = 0 :=
begin
  split;
  intro h,
  { split,
    exact aux h,
    exact aux (by linarith : y^2 + x^2 = 0), },
  { cases h with hr1 hr2,
    rw hr1,
    rw hr2,
    norm_num, }
end
-- END


import subsequence -- Imports sequences and their properties, as well as
                   -- tools for constructing and manipulating subsequences.

namespace my_analysis

  /-- Proposition that a sequence is Cauchy, that is that its terms grow arbitrarily close together. -/
  def seq.cauchy (s : seq) : Prop := ∀ ⦃ε⦄, ε > 0 → ∃ B : ℕ, ∀ ⦃m n⦄, B ≤ m → B ≤ n → |s m - s n| < ε

  /-- Proof that Cauchy sequences are bounded. -/
  theorem cauchy_is_bounded {s : seq} (hc : s.cauchy): s.bounded :=
  begin
    cases hc zero_lt_one with B h,
    have hb : ∀ ⦃n⦄, B ≤ n → |s n| < 1 + |s B|,
      intros n hn,
      have h₁ : |s n| ≤ |s n - s B| + |s B|,
        conv_lhs { rw [← sub_add_cancel (s n) (s B)] },
        exact abs_add (s n - s B) (s B),
      have h₂ : |s n - s B| < 1, from h hn (le_refl B),
      exact lt_of_le_of_lt h₁ (add_lt_add_of_lt_of_le h₂ (le_refl _)),
    apply (bounded_shift s B).mpr,
    use 1 + |s B|, intros,
    exact le_of_lt (hb le_add_self)
  end

  /-- Proof that Cauchy and convergent are equivalent properties. -/
  theorem convergent_iff_cauchy {s : seq} : s.convergent ↔ s.cauchy :=
  begin
    split,
    { -- convergent → cauchy
      rintros ⟨x, hx⟩ ε hε,
      cases hx (half_pos hε) with B h,
      use B, intros m n hm hn,
      rw [← add_halves ε],
      refine lt_of_le_of_lt _ (add_lt_add (h hm) (h hn)),
      have : |s m - s n| = |(s m - x) + (x - s n)|, ring_nf,
      rw [this, ← abs_neg (s n - x), neg_sub],
      exact abs_add (s m - x) (x - s n) },
    { -- cauchy → convergent
      intro hc,
      -- We get the limit from the convergent subsequence `bolzano_weierstrass` gives us.
      cases bolzano_weierstrass (cauchy_is_bounded hc) with si hcon,
      cases hcon with x hx, use x, intros ε hε,
      cases hx (half_pos hε) with B hB,
      cases hc (half_pos hε) with C hC,
      let k := max B C,
      have hk : C ≤ si k,
        apply (si.unbounded C).trans,
        by_cases h : C = k,
        { rw [h] },
        { exact le_of_lt (si.mono (lt_of_le_of_ne (le_max_right B C) h)) },
      use C, intros n hn,
      rw [← add_halves ε],
      refine lt_of_le_of_lt _ (add_lt_add (hC hn hk) (hB (le_max_left B C))),
      have : |s n - x| = |(s n - s.subseq si k) + (s.subseq si k - x)|, ring_nf,
      rw [this], exact abs_add _ _ }
  end

end my_analysis


-- Definiciones de límite
-- ======================

import topology.instances.real

notation `|` x `|` := abs x

definition is_limit (a : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε

open filter

open_locale topological_space

lemma is_limit_iff_tendsto
  (a : ℕ → ℝ)
  (l : ℝ)
  : is_limit a l ↔ tendsto a at_top (𝓝 l) :=
begin
  split,
  { intros h X hX,
    rw mem_nhds_iff_exists_Ioo_subset at hX,
    rcases hX with ⟨x, y, ⟨hxl, hly⟩, h2⟩,
    set ε := min (l - x) (y - l) with hε,
    have hε_pos : 0 < ε := lt_min (by linarith) (by linarith),
    obtain ⟨N, hN⟩ := h ε hε_pos,
    rw [mem_map, mem_at_top_sets],
    use N,
    intros n hn,
    specialize hN n hn,
    apply h2,
    rw abs_lt at hN,
    cases hN,
    have hε1 : ε ≤ l - x := min_le_left _ _,
    have hε2 : ε ≤ y - l := min_le_right _ _,
    split;
    linarith },
  { intros h ε hε,
    rw tendsto_nhds at h,
    specialize h (set.Ioo (l - ε) (l + ε)) (is_open_Ioo) ⟨by linarith, by linarith⟩,
    rw mem_at_top_sets at h,
    rcases h with ⟨N, hN⟩,
    use N,
    intros n hn,
    obtain ⟨h1, h2⟩ := hN n hn,
    rw abs_lt,
    split; linarith },
end

example
  (a b : ℕ → ℝ)
  (l m : ℝ)
  : is_limit a l → is_limit b m → is_limit (λ n, a n + b n) (l + m) :=
begin
  simp only [is_limit_iff_tendsto],
  exact tendsto.add,
end

example
  (a b : ℕ → ℝ)
  (l m : ℝ)
  : is_limit a l → is_limit b m → is_limit (λ n, a n * b n) (l * m) :=
begin
  simp only [is_limit_iff_tendsto],
  exact tendsto.mul,
end


import LeanCodePrompts.ExploreTranslate
import Mathlib

/-
def eg1 := "There are infinitely many odd numbers."

#eval translate eg1

#eval showLogs 1

def eg2 := "Every set of `10` distinct numbers between `1` and `100` contains two disjoint nonempty subsets with the same sum."

#eval translate eg2

def eg3 := "If a set `S` contains `0` and `1`, and the mean of every finite nonempty subset of `S`, then `S` contains all the rational numbers in the unit interval."

#eval translate eg3

#eval showLogs 1

def eg4 := "Every sequence of natural numbers is bounded."

#eval translate eg4

def eg5 := "Every sequence `x_n` of natural numbers is constant."

#eval translate eg5

def eg6 := "Every finite sequence `x_1, x_2, ..., x_n` of natural numbers is bounded."

#eval translate eg6

#eval showLogs 1

def eg7 := "If a set of natural numbers `S` contains `0` and `1`, and the mean of every finite nonempty subset of `S`, then `S` is non-empty."

#eval translate eg7

-- #eval showLogs 1

#eval dotName? "S.nonempty"

def eg8 := "Every complete pseudometric space is a Baire space."

#eval translate eg8

-/

def eg9 := "If $n$ is a prime number, then $$\\Phi_n(x) = 1+x+x^2+\\cdots+x^{n-1}=\\sum_{k=0}^{n-1}x^k$$, where $\\Phi_n$ is the $n$-th cyclotomic polynomial."

-- Formula translation incorrectly, must try with partial translation to Lean + Unicode
#eval translate eg9

def eg10 := "The Möbius inversion formula allows the expression of the $n$-th cyclotomic polynomial $\\Phi_n(x)$ as an explicit rational fraction $$\\Phi_n(x)=\\prod_{d\\mid n}(x^d-1)^{\\mu \\left(\\frac{n}{d} \\right)$$, where $\\mu$ is the Möbius function."

-- not working for some reason
-- #eval translate eg10

def eg11 := "The function `ψ : ℝ → ℝ` that takes the value $\\exp((-1)/(1 - x^2))$ on $(-1, 1)$ and $0$ everywhere else is smooth and compactly supported."

#eval translate eg11

-- this example is in `mathlib`
def eg12 := "If `f : ℂ → E` is continuous on a closed disc of radius $R$ and is complex differentiable at all but countably many points of its interior, then the integral $\\oint_{|z-c|=R} \\frac{f(z)}{z-c}\\,dz$ is equal to $2πiy$."

#eval translate eg12

-- Initial translation failed to recognise `tangent function` as a synonym/expansion of `tan`. Adding `tan` explicitly seems to have fixed this
def eg13 := "The tangent function `tan` is periodic with period `π`."

#eval translate eg13

#eval showLogs 1

-- this workss
-- def eg14 := "The function `exp(-x)` tends to `0` as `x` tends to `∞`."

-- #eval translate eg14

-- this works
-- def eg15 := "Every finite field has non-zero characteristic."

-- #eval translate eg15

def eg16 := "Functors between categories preserve composition of morphisms."

#eval translate eg16

def eg17 := "If a function $f:\\mathbb{C} \to \\mathbb C}$ is entire and non-constant, then the set of values that $f(z)$ assumes is either the whole complex plane or the plane minus a single point."

#eval translate eg17


import tactic
import data.real.basic
import data.pnat.basic

local notation `|` x `|` := abs x

def is_limit (a : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε

def tends_to_plus_infinity (a : ℕ → ℝ) : Prop :=
∀ B, ∃ N, ∀ n ≥ N, B < a n

def is_convergent (a : ℕ → ℝ) : Prop :=
∃ l : ℝ, is_limit a l

namespace sheet_five

theorem Q1a (x : ℝ) (hx : 0 < x) (n : ℕ) : (1 : ℝ) + n * x ≤ (1 + x)^n :=
begin
  sorry
end

theorem Q1b (x : ℝ) (hx : 0 < x) : is_limit (λ n, (1 + x) ^ (-(n : ℤ))) 0 :=
begin
  intros ε hε,
  obtain ⟨N, hN⟩ := add_one_pow_unbounded_of_pos (1/ε) hx,
  existsi N,
  intros n hn,
  dsimp only,
  rw sub_zero,
  have hx2 : 0 < 1 + x,
    linarith,
  have hx3 : ∀ m, (1+x) ^ m > 0,
    intro m,
    exact fpow_pos_of_pos hx2 m,
  rw abs_of_pos (hx3 _),
  simp,
  rw ← one_div,
  rw one_div_lt (pow_pos hx2 n) hε,
  refine lt_of_lt_of_le hN _,
  rw add_comm,
  apply pow_le_pow,
    linarith,
  assumption,
end

theorem Q1c (r : ℝ) (hr : r ∈ set.Ioo (0 : ℝ) 1) : is_limit (λ n, r ^ n) 0 :=
begin
  sorry
end

theorem Q1d (r : ℝ) (hr : 1 < r) : tends_to_plus_infinity (λ n, r ^ n) :=
begin
  sorry
end


import data.real.basic

#check ∀ x y ε : ℝ, 0 < ε → ε ≤ 1 → abs x < ε → abs y < ε → abs (x * y) < ε

lemma my_lemma : ∀ {x y ε : ℝ},
  0 < ε → ε ≤ 1 → abs x < ε → abs y < ε → abs (x * y) < ε :=
begin
  intros x y ε epos ele1 xlt ylt,
  calc
    abs (x * y) = abs x * abs y : by rw abs_mul
    ... ≤ abs x * ε             :
      mul_le_mul (le_of_eq (eq.refl (abs x))) (le_of_lt ylt) (abs_nonneg y) (abs_nonneg x)
    ... < 1 * ε                 :
      (mul_lt_mul_right epos).mpr (lt_of_lt_of_le xlt ele1)
    ... = ε                     : one_mul ε
end

section
variables (f g : ℝ → ℝ) (a b : ℝ)

def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a
def fn_lb (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, a ≤ f x

example (hfa : fn_ub f a) (hgb : fn_ub g b) :
  fn_ub (λ x, f x + g x) (a + b) :=
begin
  intro x,
  dsimp,
  apply add_le_add,
  apply hfa,
  apply hgb,
end

example (hfa : fn_lb f a) (hgb : fn_lb g b) :
  fn_lb (λ x, f x + g x) (a + b) :=
begin
  intro x,
  change a + b ≤ f x + g x,
  apply add_le_add,
  apply hfa,
  apply hgb,
end

example (nnf : fn_lb f 0) (nng : fn_lb g 0) :
  fn_lb (λ x, f x * g x) 0 :=
begin
  intro x,
  change 0 ≤ f x * g x,
  apply mul_nonneg,
  apply nnf,
  apply nng,
end

example (hfa : fn_ub f a) (hfb : fn_ub g b)
    (nng : fn_lb g 0) (nna : 0 ≤ a) :
  fn_ub (λ x, f x * g x) (a * b) :=
begin
  intro x,
  change f x * g x ≤ a * b,
  apply mul_le_mul,
  apply hfa,
  apply hfb,
  apply nng,
  exact nna,
end

end

section
variables {α : Type*} {R : Type*} [ordered_cancel_add_comm_monoid R]

#check @add_le_add

def fn_ub' (f : α → R) (a : R) : Prop := ∀ x, f x ≤ a

theorem fn_ub_add {f g : α → R} {a b : R}
    (hfa : fn_ub' f a) (hgb : fn_ub' g b) :
  fn_ub' (λ x, f x + g x) (a + b) :=
λ x, add_le_add (hfa x) (hgb x)

end

section
example (f : ℝ → ℝ) (h : monotone f) :
  ∀ {a b}, a ≤ b → f a ≤ f b := h

variables (f g : ℝ → ℝ)

example (mf : monotone f) (mg : monotone g) :
  monotone (λ x, f x + g x) :=
begin
  intros a b aleb,
  dsimp,
  apply add_le_add,
  apply mf aleb,
  apply mg aleb,
end

example {c : ℝ} (mf : monotone f) (nnc : 0 < c) :
  monotone (λ x, c * f x) :=
begin
  intros a b aleb,
  dsimp,
  apply (mul_le_mul_left nnc).mpr,
  apply mf aleb,
end

example (mf : monotone f) (mg : monotone g) :
  monotone (λ x, f (g x)) :=
λ a b aleb, mf (mg aleb)

end

section
variables (f g : ℝ → ℝ)
def fn_even (f : ℝ → ℝ) : Prop := ∀ x, f x = f (-x)
def fn_odd (f : ℝ → ℝ) : Prop := ∀ x, f x = - f (-x)

example (ef : fn_even f) (eg : fn_even g) : fn_even (λ x, f x + g x) :=
begin
  intro x,
  calc
    (λ x, f x + g x) x = f x + g x       : rfl
                    ... = f (-x) + g (-x) : by rw [ef, eg]
end

example (of : fn_odd f) (og : fn_odd g) : fn_even (λ x, f x * g x) :=
begin
  intro x,
  dsimp,
  rw [of, og],
  apply neg_mul_neg,
end

example (ef : fn_even f) (og : fn_odd g) : fn_odd (λ x, f x * g x) :=
begin
  intro x,
  dsimp,
  rw [ef, og],
  apply mul_neg,
end

example (ef : fn_even f) (og : fn_odd g) : fn_even (λ x, f (g x)) :=
begin
  intro x,
  dsimp,
  rw [ef, og],
  rw neg_neg,
end

end

/- Sets -/

section
variables {α : Type*} (r s t : set α)

example : s ⊆ s :=
by { intros x xs, exact xs }

theorem subset.refl : s ⊆ s := λ x xs, xs

theorem subset.trans : r ⊆ s → s ⊆ t → r ⊆ t :=
begin
  intros rs st x xin,
  apply st,
  apply rs,
  exact xin,
end

end

section
variables {α : Type*} [partial_order α]
variables (s : set α) (a b : α)

def set_ub (s : set α) (a : α) := ∀ x, x ∈ s → x ≤ a

example (h : set_ub s a) (h' : a ≤ b) : set_ub s b :=
begin
  intros x xs,
  apply le_trans,
  apply h,
  apply xs,
  exact h',
end

end

section
open function

example (c : ℝ) : injective (λ x, x + c) :=
begin
  intros x₁ x₂ h',
  exact (add_left_inj c).mp h',
end

example {c : ℝ} (h : c ≠ 0) : injective (λ x, c * x) :=
begin
  intros a b,
  dsimp,
  intro heq,
  cases (mul_eq_mul_left_iff.mp heq),
  { assumption,},
  { contradiction,}
end

variables {α : Type*} {β : Type*} {γ : Type*}
variables {g : β → γ} {f : α → β}

example (injg : injective g) (injf : injective f) :
  injective (λ x, g (f x)) :=
begin
  intros a b,
  dsimp,
  intro heq,
  apply injf,
  apply injg,
  exact heq,
end

end


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

! This file was ported from Lean 3 source module order.partial_sups
! leanprover-community/mathlib commit d6fad0e5bf2d6f48da9175d25c3dc5706b3834ce
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Finset.Lattice
import Mathlib.Order.Hom.Basic
import Mathlib.Order.ConditionallyCompleteLattice.Finset

/-!
# The monotone sequence of partial supremums of a sequence

We define `partialSups : (ℕ → α) → ℕ →o α` inductively. For `f : ℕ → α`, `partialSups f` is
the sequence `f 0 `, `f 0 ⊔ f 1`, `f 0 ⊔ f 1 ⊔ f 2`, ... The point of this definition is that
* it doesn't need a `⨆`, as opposed to `⨆ (i ≤ n), f i` (which also means the wrong thing on
  `ConditionallyCompleteLattice`s).
* it doesn't need a `⊥`, as opposed to `(Finset.range (n + 1)).sup f`.
* it avoids needing to prove that `Finset.range (n + 1)` is nonempty to use `Finset.sup'`.

Equivalence with those definitions is shown by `partialSups_eq_bsupᵢ`, `partialSups_eq_sup_range`,
and `partialSups_eq_sup'_range` respectively.

## Notes

One might dispute whether this sequence should start at `f 0` or `⊥`. We choose the former because :
* Starting at `⊥` requires... having a bottom element.
* `fun f n ↦ (Finset.range n).sup f` is already effectively the sequence starting at `⊥`.
* If we started at `⊥` we wouldn't have the Galois insertion. See `partialSups.gi`.

## TODO

One could generalize `partialSups` to any locally finite bot preorder domain, in place of `ℕ`.
Necessary for the TODO in the module docstring of `Order.disjointed`.
-/

variable {α : Type _}

section SemilatticeSup

variable [SemilatticeSup α]

/-- The monotone sequence whose value at `n` is the supremum of the `f m` where `m ≤ n`. -/
def partialSups (f : ℕ → α) : ℕ →o α :=
  ⟨@Nat.rec (fun _ => α) (f 0) fun (n : ℕ) (a : α) => a ⊔ f (n + 1),
    monotone_nat_of_le_succ fun _ => le_sup_left⟩
#align partial_sups partialSups

@[simp]
theorem partialSups_zero (f : ℕ → α) : partialSups f 0 = f 0 :=
  rfl
#align partial_sups_zero partialSups_zero

@[simp]
theorem partialSups_succ (f : ℕ → α) (n : ℕ) :
    partialSups f (n + 1) = partialSups f n ⊔ f (n + 1) :=
  rfl
#align partial_sups_succ partialSups_succ

theorem le_partialSups_of_le (f : ℕ → α) {m n : ℕ} (h : m ≤ n) : f m ≤ partialSups f n := by
  induction' n with n ih
  · rw [nonpos_iff_eq_zero.mp h, partialSups_zero]
  · cases' h with h h
    · exact le_sup_right
    · exact (ih h).trans le_sup_left
#align le_partial_sups_of_le le_partialSups_of_le

theorem le_partialSups (f : ℕ → α) : f ≤ partialSups f := fun _n => le_partialSups_of_le f le_rfl
#align le_partial_sups le_partialSups

theorem partialSups_le (f : ℕ → α) (n : ℕ) (a : α) (w : ∀ m, m ≤ n → f m ≤ a) :
    partialSups f n ≤ a := by
  induction' n with n ih
  · apply w 0 le_rfl
  · exact sup_le (ih fun m p => w m (Nat.le_succ_of_le p)) (w (n + 1) le_rfl)
#align partial_sups_le partialSups_le

@[simp]
theorem bddAbove_range_partialSups {f : ℕ → α} :
    BddAbove (Set.range (partialSups f)) ↔ BddAbove (Set.range f) := by
  apply exists_congr fun a => _
  intro a
  constructor
  · rintro h b ⟨i, rfl⟩
    exact (le_partialSups _ _).trans (h (Set.mem_range_self i))
  · rintro h b ⟨i, rfl⟩
    exact partialSups_le _ _ _ fun _ _ => h (Set.mem_range_self _)
#align bdd_above_range_partial_sups bddAbove_range_partialSups

theorem Monotone.partialSups_eq {f : ℕ → α} (hf : Monotone f) : (partialSups f : ℕ → α) = f := by
  ext n
  induction' n with n ih
  · rfl
  · rw [partialSups_succ, ih, sup_eq_right.2 (hf (Nat.le_succ _))]
#align monotone.partial_sups_eq Monotone.partialSups_eq

theorem partialSups_mono : Monotone (partialSups : (ℕ → α) → ℕ →o α) := by
  rintro f g h n
  induction' n with n ih
  · exact h 0
  · exact sup_le_sup ih (h _)
#align partial_sups_mono partialSups_mono

/-- `partialSups` forms a Galois insertion with the coercion from monotone functions to functions.
-/
def partialSups.gi : GaloisInsertion (partialSups : (ℕ → α) → ℕ →o α) (↑) where
  choice f h :=
    ⟨f, by convert (partialSups f).monotone using 1; exact (le_partialSups f).antisymm h⟩
  gc f g := by
    refine' ⟨(le_partialSups f).trans, fun h => _⟩
    convert partialSups_mono h
    exact OrderHom.ext _ _ g.monotone.partialSups_eq.symm
  le_l_u f := le_partialSups f
  choice_eq f h := OrderHom.ext _ _ ((le_partialSups f).antisymm h)
#align partial_sups.gi partialSups.gi

theorem partialSups_eq_sup'_range (f : ℕ → α) (n : ℕ) :
    partialSups f n = (Finset.range (n + 1)).sup' ⟨n, Finset.self_mem_range_succ n⟩ f := by
  induction' n with n ih
  · simp
  · dsimp [partialSups] at ih⊢
    simp_rw [@Finset.range_succ n.succ]
    rw [ih, Finset.sup'_insert, sup_comm]
#align partial_sups_eq_sup'_range partialSups_eq_sup'_range

end SemilatticeSup

theorem partialSups_eq_sup_range [SemilatticeSup α] [OrderBot α] (f : ℕ → α) (n : ℕ) :
    partialSups f n = (Finset.range (n + 1)).sup f := by
  induction' n with n ih
  · simp
  · dsimp [partialSups] at ih⊢
    rw [Finset.range_succ, Finset.sup_insert, sup_comm, ih]
#align partial_sups_eq_sup_range partialSups_eq_sup_range

/- Note this lemma requires a distributive lattice, so is not useful (or true) in situations such as
submodules. -/
theorem partialSups_disjoint_of_disjoint [DistribLattice α] [OrderBot α] (f : ℕ → α)
    (h : Pairwise (Disjoint on f)) {m n : ℕ} (hmn : m < n) : Disjoint (partialSups f m) (f n) := by
  induction' m with m ih
  · exact h hmn.ne
  · rw [partialSups_succ, disjoint_sup_left]
    exact ⟨ih (Nat.lt_of_succ_lt hmn), h hmn.ne⟩
#align partial_sups_disjoint_of_disjoint partialSups_disjoint_of_disjoint

section ConditionallyCompleteLattice

variable [ConditionallyCompleteLattice α]

theorem partialSups_eq_csupᵢ_Iic (f : ℕ → α) (n : ℕ) : partialSups f n = ⨆ i : Set.Iic n, f i := by
  have : Set.Iio (n + 1) = Set.Iic n := Set.ext fun _ => Nat.lt_succ_iff
  rw [partialSups_eq_sup'_range, Finset.sup'_eq_csupₛ_image, Finset.coe_range, supᵢ, this]
  simp only [Set.range, Subtype.exists, Set.mem_Iic, exists_prop, (· '' ·)]
#align partial_sups_eq_csupr_Iic partialSups_eq_csupᵢ_Iic

@[simp]
theorem csupᵢ_partialSups_eq {f : ℕ → α} (h : BddAbove (Set.range f)) :
    (⨆ n, partialSups f n) = ⨆ n, f n := by
  refine' (csupᵢ_le fun n => _).antisymm (csupᵢ_mono _ <| le_partialSups f)
  · rw [partialSups_eq_csupᵢ_Iic]
    exact csupᵢ_le fun i => le_csupᵢ h _
  · rwa [bddAbove_range_partialSups]
#align csupr_partial_sups_eq csupᵢ_partialSups_eq

end ConditionallyCompleteLattice

section CompleteLattice

variable [CompleteLattice α]

theorem partialSups_eq_bsupᵢ (f : ℕ → α) (n : ℕ) : partialSups f n = ⨆ i ≤ n, f i := by
  simpa only [supᵢ_subtype] using partialSups_eq_csupᵢ_Iic f n
#align partial_sups_eq_bsupr partialSups_eq_bsupᵢ

-- Porting note: simp can prove this @[simp]
theorem supᵢ_partialSups_eq (f : ℕ → α) : (⨆ n, partialSups f n) = ⨆ n, f n :=
  csupᵢ_partialSups_eq <| OrderTop.bddAbove _
#align supr_partial_sups_eq supᵢ_partialSups_eq

theorem supᵢ_le_supᵢ_of_partialSups_le_partialSups {f g : ℕ → α}
    (h : partialSups f ≤ partialSups g) : (⨆ n, f n) ≤ ⨆ n, g n := by
  rw [← supᵢ_partialSups_eq f, ← supᵢ_partialSups_eq g]
  exact supᵢ_mono h
#align supr_le_supr_of_partial_sups_le_partial_sups supᵢ_le_supᵢ_of_partialSups_le_partialSups

theorem supᵢ_eq_supᵢ_of_partialSups_eq_partialSups {f g : ℕ → α}
    (h : partialSups f = partialSups g) : (⨆ n, f n) = ⨆ n, g n := by
  simp_rw [← supᵢ_partialSups_eq f, ← supᵢ_partialSups_eq g, h]
#align supr_eq_supr_of_partial_sups_eq_partial_sups supᵢ_eq_supᵢ_of_partialSups_eq_partialSups

end CompleteLattice


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

import topology.algebra.group
import logic.function.iterate

/-!
# Flows and invariant sets

This file defines a flow on a topological space `α` by a topological
monoid `τ` as a continuous monoid-act of `τ` on `α`. Anticipating the
cases where `τ` is one of `ℕ`, `ℤ`, `ℝ⁺`, or `ℝ`, we use additive
notation for the monoids, though the definition does not require
commutativity.

A subset `s` of `α` is invariant under a family of maps `ϕₜ : α → α`
if `ϕₜ s ⊆ s` for all `t`. In many cases `ϕ` will be a flow on
`α`. For the cases where `ϕ` is a flow by an ordered (additive,
commutative) monoid, we additionally define forward invariance, where
`t` ranges over those elements which are nonnegative.

Additionally, we define such constructions as the restriction of a
flow onto an invariant subset, and the time-reveral of a flow by a
group.
-/

open set function filter

/-!
### Invariant sets
-/

section invariant

variables {τ : Type*} {α : Type*}

/-- A set `s ⊆ α` is invariant under `ϕ : τ → α → α` if
    `ϕ t s ⊆ s` for all `t` in `τ`. -/
def is_invariant (ϕ : τ → α → α) (s : set α): Prop := ∀ t, maps_to (ϕ t) s s

variables (ϕ : τ → α → α) (s : set α)

lemma is_invariant_iff_image : is_invariant ϕ s ↔ ∀ t, ϕ t '' s ⊆ s :=
by simp_rw [is_invariant, maps_to']

/-- A set `s ⊆ α` is forward-invariant under `ϕ : τ → α → α` if
    `ϕ t s ⊆ s` for all `t ≥ 0`. -/
def is_fw_invariant [preorder τ] [has_zero τ] (ϕ : τ → α → α) (s : set α): Prop :=
∀ ⦃t⦄, 0 ≤ t → maps_to (ϕ t) s s

lemma is_invariant.is_fw_invariant [preorder τ] [has_zero τ] {ϕ : τ → α → α} {s : set α}
  (h : is_invariant ϕ s) : is_fw_invariant ϕ s :=
λ t ht, h t

/-- If `τ` is a `canonically_ordered_add_monoid` (e.g., `ℕ` or `ℝ≥0`), then the notions
`is_fw_invariant` and `is_invariant` are equivalent. -/
lemma is_fw_invariant.is_invariant [canonically_ordered_add_monoid τ] {ϕ : τ → α → α} {s : set α}
  (h : is_fw_invariant ϕ s) : is_invariant ϕ s :=
λ t, h (zero_le t)

/-- If `τ` is a `canonically_ordered_add_monoid` (e.g., `ℕ` or `ℝ≥0`), then the notions
`is_fw_invariant` and `is_invariant` are equivalent. -/
lemma is_fw_invariant_iff_is_invariant [canonically_ordered_add_monoid τ]
  {ϕ : τ → α → α} {s : set α} :
  is_fw_invariant ϕ s ↔ is_invariant ϕ s :=
⟨is_fw_invariant.is_invariant, is_invariant.is_fw_invariant⟩

end invariant

/-!
### Flows
-/

/-- A flow on a topological space `α` by an a additive topological
    monoid `τ` is a continuous monoid action of `τ` on `α`.-/
structure flow
  (τ : Type*) [topological_space τ] [add_monoid τ] [has_continuous_add τ]
  (α : Type*) [topological_space α] :=
(to_fun    : τ → α → α)
(cont'     : continuous (uncurry to_fun))
(map_add'  : ∀ t₁ t₂ x, to_fun (t₁ + t₂) x = to_fun t₁ (to_fun t₂ x))
(map_zero' : ∀ x, to_fun 0 x = x)

namespace flow

variables
{τ : Type*} [add_monoid τ] [topological_space τ] [has_continuous_add τ]
{α : Type*} [topological_space α]
(ϕ : flow τ α)

instance : inhabited (flow τ α) :=
⟨{ to_fun    := λ _ x, x,
   cont'     := continuous_snd,
   map_add'  := λ _ _ _, rfl,
   map_zero' := λ _, rfl }⟩

instance : has_coe_to_fun (flow τ α) (λ _, τ → α → α) := ⟨flow.to_fun⟩

@[ext]

@[continuity]
protected lemma continuous {β : Type*} [topological_space β]
  {t : β → τ} (ht : continuous t) {f : β → α} (hf : continuous f) :
  continuous (λ x, ϕ (t x) (f x)) :=
ϕ.cont'.comp (ht.prod_mk hf)

alias flow.continuous ← continuous.flow

lemma map_add (t₁ t₂ : τ) (x : α) : ϕ (t₁ + t₂) x = ϕ t₁ (ϕ t₂ x) :=
ϕ.map_add' _ _ _

@[simp] lemma map_zero : ϕ 0 = id := funext ϕ.map_zero'

lemma map_zero_apply (x : α) : ϕ 0 x = x := ϕ.map_zero' x

/-- Iterations of a continuous function from a topological space `α`
    to itself defines a semiflow by `ℕ` on `α`. -/
def from_iter {g : α → α} (h : continuous g) : flow ℕ α :=
{ to_fun    := λ n x, g^[n] x,
  cont'     := continuous_uncurry_of_discrete_topology_left (continuous.iterate h),
  map_add'  := iterate_add_apply _,
  map_zero' := λ x, rfl }

/-- Restriction of a flow onto an invariant set. -/
def restrict {s : set α} (h : is_invariant ϕ s) : flow τ ↥s :=
{ to_fun    := λ t, (h t).restrict _ _ _,
  cont'     := continuous_subtype_mk _ (ϕ.continuous continuous_fst
    (continuous_subtype_coe.comp continuous_snd)),
  map_add'  := λ _ _ _, subtype.ext (map_add _ _ _ _),
  map_zero' := λ _, subtype.ext (map_zero_apply _ _)}

end flow

namespace flow

variables
{τ : Type*} [add_comm_group τ] [topological_space τ] [topological_add_group τ]
{α : Type*} [topological_space α]
(ϕ : flow τ α)

lemma is_invariant_iff_image_eq (s : set α) :
  is_invariant ϕ s ↔ ∀ t, ϕ t '' s = s :=
(is_invariant_iff_image _ _).trans (iff.intro
  (λ h t, subset.antisymm (h t) (λ _ hx, ⟨_, h (-t) ⟨_, hx, rfl⟩, by simp [← map_add]⟩))
  (λ h t, by rw h t))

/-- The time-reversal of a flow `ϕ` by a (commutative, additive) group
    is defined `ϕ.reverse t x = ϕ (-t) x`. -/
def reverse : flow τ α :=
{ to_fun    := λ t, ϕ (-t),
  cont'     := ϕ.continuous continuous_fst.neg continuous_snd,
  map_add'  := λ _ _ _, by rw [neg_add, map_add],
  map_zero' := λ _, by rw [neg_zero, map_zero_apply] }

/-- The map `ϕ t` as a homeomorphism. -/
def to_homeomorph (t : τ) : α ≃ₜ α :=
{ to_fun := ϕ t,
  inv_fun := ϕ (-t),
  left_inv := λ x, by rw [← map_add, neg_add_self, map_zero_apply],
  right_inv := λ x, by rw [← map_add, add_neg_self, map_zero_apply] }

lemma image_eq_preimage (t : τ) (s : set α) : ϕ t '' s = ϕ (-t) ⁻¹' s :=
(ϕ.to_homeomorph t).to_equiv.image_eq_preimage s

end flow


namespace chap8ex1

    open function

    #print surjective

    universes u v w
    variables {α : Type u} {β : Type v} {γ : Type w}
    open function

    lemma composition_check {g : β → γ} {f : α → β} : α → γ := g ∘ f

    lemma surjective_comp {g : β → γ} {f : α → β} (hg : surjective g) (hf : surjective f) :
    surjective (g ∘ f) :=
    λ lg,
    match (hg lg) with exists.intro (b: β) (beq : g b = lg) :=
        match (hf b) with exists.intro (a : α) (aeq : f a = b) :=
            begin
                existsi a,
                simp [aeq, beq]
            end
        end
    end

end chap8ex1

namespace chap8ex2

    namespace hidden

        open nat (zero succ)

        def addition : ℕ → ℕ → ℕ
        | m 0 := m
        | m (succ n) := succ (addition m n)

        lemma eight_plus_five_is_thirteen : addition 8 5 = 13 := rfl

        lemma zero_add : ∀ (n : ℕ), addition 0 n = n
        | 0 := by refl
        | (succ 0) := by refl
        | (succ n) := by {
            have s1 : addition 0 n = n, by rw [zero_add n],
            have s2 : addition 0 (succ n) = succ (addition 0 n), from rfl,
            rw [s2, s1]
        }

        lemma add_zero : ∀ (n : ℕ), addition n 0 = n
        | 0 := by refl
        | (succ n) := by refl

        lemma add_one : ∀ n, addition n 1 = succ n
        | 0 := rfl
        | (succ n) := rfl

        lemma one_add : ∀ n, addition 1 n = succ n
        | 0 := by rw [add_zero]
        | (nat.succ npred) := (
            have ih : addition 1 npred = succ npred, from one_add npred,
            have addition 1 (succ npred) = succ (addition 1 npred), from rfl,
            by rw [this, ih]
        )

        lemma m_add_succ (m : ℕ) : ∀ n, addition (succ m) n = succ (addition m n)
        | 0 := rfl
        | (nat.succ npred) := by {
            have ih : addition (succ m) npred = succ (addition m npred), by rw [m_add_succ npred],
            have s1 : addition (succ m) (succ npred) = succ (addition (succ m) npred), from rfl,
            have s2 : succ (addition (succ m) npred) = succ (succ (addition m npred)), by rw [ih],
            have s3 : succ (succ (addition m npred)) = succ (addition m (succ npred)), by refl,
            rw [s1, s2, s3]
        }

        lemma one_add' (n : ℕ): addition 1 n = succ n :=
        have addition 1 n = succ (addition 0 n), by rw m_add_succ,
        by rw [this, zero_add]

        lemma add_succ_commutes : ∀ m n, addition (succ m) n = addition m (succ n)
        | m n := (
            have f1 : addition (succ m) n = succ (addition m n), by rw [m_add_succ m],
            have f2 : addition m (succ n) = succ (addition m n), by rw [addition],
            by simp [f1, f2]
        )

        theorem addition_comm : ∀ (m n : ℕ), addition m n = addition n m
        | 0 n := by rw [add_zero, zero_add]
        | (succ mpred) n :=
            calc
                addition (succ mpred) n = succ (addition mpred n) : by rw m_add_succ
                    ... = succ (addition n mpred) : by rw [addition_comm]
                    ... = addition n (succ mpred) : rfl

        theorem addition_assoc : ∀ (l m n : ℕ), addition (addition l m) n = addition l (addition m n)
        | l m 0 := by rw [add_zero, add_zero]
        | l m (succ npred) :=
            calc
                addition (addition l m) (succ npred) = succ (addition (addition l m) npred) : rfl
                    ... = succ (addition l (addition m npred)) : by rw [addition_assoc]
                    ... = addition l (succ (addition m npred)) : by refl
                    ... = addition l (addition m (succ npred)) : by refl

        -- MULTIPLICATION --

        def multiplication : ℕ → ℕ → ℕ
        | 0 n := 0
        | m 0 := 0
        | m (succ n) := addition (multiplication m n) m

        lemma two_times_three_is_six : multiplication 2 3 = 6 := rfl

        theorem mul_zero : ∀ n, multiplication n 0 = 0
        | 0 := rfl
        | (succ _) := rfl

        theorem zero_mul : ∀ n, multiplication 0 n = 0
        | 0 := rfl
        | (succ _) := rfl

        theorem mul_one : ∀ n, multiplication n 1 = n
        | 0 := rfl
        | (succ n) :=
            calc
                multiplication (succ n) 1 = addition (multiplication (succ n) 0) (succ n) : rfl
                    ... = addition 0 (succ n) : rfl
                    ... = succ n : by rw zero_add

        theorem one_mul : ∀ n, multiplication 1 n = n
        | 0 := rfl
        | (succ n) :=
            calc
                multiplication 1 (succ n) = multiplication 1 n + 1 : rfl
                    ... = n + 1 : by rw [one_mul n]
                    ... = succ n : rfl

        theorem mul_add_once : ∀ m n, addition (multiplication m n) n = multiplication (succ m) n
        | 0 0 := rfl
        | 0 (succ n) := by rw [one_mul, zero_mul, zero_add]
        | (succ m) 0 := by rw [mul_zero, mul_zero, add_zero]
        | (succ m) (succ n) :=
            calc
                addition (multiplication (succ m) (succ n)) (succ n) = addition (addition (multiplication (succ m) n) (succ m)) (succ n) : rfl
                    ... = addition (multiplication (succ m) n) (addition (succ m) (succ n)) : by rw [addition_assoc]
                    ... = addition (multiplication (succ m) n) (addition (succ n) (succ m)) : by rw [addition_comm (succ m)]
                    ... = addition (addition (multiplication (succ m) n) n) (succ (succ m)) : by rw [add_succ_commutes, addition_assoc]
                    ... = addition (multiplication (succ (succ m)) n) (succ (succ m)) : by rw [mul_add_once]
                    ... = multiplication (succ (succ m)) (succ n) : rfl

        theorem mul_comm : ∀ m n, multiplication m n = multiplication n m
        | 0 0 := rfl
        | 0 (succ n) := by rw [zero_mul, mul_zero]
        | (succ m) 0 := by rw [zero_mul, mul_zero]
        | (succ m) (succ n) :=
            calc
                multiplication (succ m) (succ n) = addition (multiplication (succ m) n) (succ m) : rfl
                    ... = addition (multiplication n (succ m)) (succ m) : by rw [mul_comm]
                    ... = multiplication (succ n) (succ m) : by rw [mul_add_once]

        theorem mul_distrib : ∀ l m n, multiplication l (addition m n) = addition (multiplication l m) (multiplication l n)
        | 0 _ _ := by rw [zero_mul, zero_mul, zero_mul, add_zero]
        | _ _ 0 := by rw [add_zero, mul_zero, add_zero]
        | _ 0 _ := by rw [zero_add, mul_zero, zero_add]
        | (l+1) (m+1) (n+1) :=
            let l' := succ l, m' := succ m, n' := succ n in
            -- Looks terrible, could be made much shorter.
            calc
                multiplication l' (addition m' n') = multiplication l' (succ (addition m' n)) : rfl
                    ... = addition (multiplication l' (addition m' n)) l' : rfl
                    ... = addition (multiplication l' (succ $ addition m n)) l' : by { rw [add_succ_commutes], refl }
                    ... = addition (addition (multiplication l' (addition m n)) l') l' : rfl
                    ... = addition (addition (addition (multiplication l (addition m n)) (addition m n)) l') l' : by rw [mul_add_once]
                    ... = addition (addition (addition (addition (multiplication l m) (multiplication l n)) (addition m n)) l') l' : by rw [mul_distrib]
                    ... = addition (addition (addition (multiplication l m) (addition (multiplication l n) (addition m n))) l') l' : by rw [addition_assoc (multiplication l m)]
                    ... = addition (addition (addition (multiplication l m) (addition (addition (multiplication l n) n) m)) l') l' : by rw [addition_comm m n, addition_assoc (multiplication l n)]
                    ... = addition (addition (addition (multiplication l m) (addition (multiplication l' n) m)) l') l' : by rw [mul_add_once]
                    ... = addition (addition (addition (multiplication l m) (addition m (multiplication l' n))) l') l' : by rw [addition_comm (multiplication l' n)]
                    ... = addition (addition (addition (addition (multiplication l m) m) (multiplication l' n)) l') l' : by rw [←addition_assoc (multiplication l m)]
                    ... = addition (addition (addition (multiplication l' m) (multiplication l' n)) l') l' : by rw [mul_add_once]
                    ... = addition (addition (multiplication l' m) (addition (multiplication l' n) l')) l' : by rw [addition_assoc (multiplication l' m)]
                    ... = addition (addition (multiplication l' m) (multiplication l' n')) l' : rfl
                    ... = addition l' (addition (multiplication l' m) (multiplication l' n')) : by rw [addition_comm]
                    ... = addition (addition l' (multiplication l' m)) (multiplication l' n') : by rw [addition_assoc]
                    ... = addition (addition (multiplication l' m) l') (multiplication l' n') : by rw [addition_comm l']
                    ... = addition (multiplication l' m') (multiplication l' n') : rfl

        theorem mul_assoc : ∀ l m n, multiplication (multiplication l m) n = multiplication l (multiplication m n)
        | 0 _ _ := by rw [zero_mul, zero_mul, zero_mul]
        | _ 0 _ := by rw [mul_zero, zero_mul, mul_zero]
        | _ _ 0 := by rw [mul_zero, mul_zero, mul_zero]
        | (l+1) (m+1) (n+1) :=
            let l' := succ l, m' := succ m, n' := succ n in
            -- ditto
            calc
                multiplication (multiplication l' m') n' = addition (multiplication (multiplication l' m') n) (multiplication l' m') : rfl
                    ... = addition (multiplication (addition (multiplication l' m) l') n) (multiplication l' m') : rfl
                    ... = addition (multiplication (addition (addition (multiplication l m) m) l') n) (multiplication l' m') : by rw [mul_add_once]
                    ... = addition (multiplication n (addition (addition (multiplication l m) m) l')) (multiplication l' m') : by rw [mul_comm]
                    ... = addition (addition (multiplication n (addition (multiplication l m) m)) (multiplication n l')) (multiplication l' m') : by rw [mul_distrib n]
                    ... = addition (addition (addition (multiplication n (multiplication l m)) (multiplication n m)) (multiplication n l')) (multiplication l' m') : by rw [mul_distrib n]
                    ... = addition (addition (addition (multiplication (multiplication l m) n) (multiplication n m)) (multiplication n l')) (multiplication l' m') : by rw [mul_comm]
                    ... = addition (addition (addition (multiplication l (multiplication m n)) (multiplication n m)) (multiplication n l')) (multiplication l' m') : by rw [mul_assoc]
                    ... = addition (addition (addition (multiplication l (multiplication m n)) (multiplication m n)) (multiplication n l')) (multiplication l' m') : by rw [mul_comm m]
                    ... = addition (addition (multiplication l' (multiplication m n)) (multiplication n l')) (multiplication l' m') : by rw [mul_add_once]
                    ... = addition (multiplication l' (multiplication m n)) (addition (multiplication n l') (multiplication l' m')) : by rw [addition_assoc]
                    ... = addition (multiplication l' (multiplication m n)) (addition (multiplication l' n) (multiplication l' m')) : by rw [mul_comm n]
                    ... = addition (multiplication l' (multiplication m n)) (multiplication l' (addition n m')) : by rw [mul_distrib]
                    ... = multiplication l' (addition (multiplication m n) (addition n m')) : by rw [←mul_distrib]
                    ... = multiplication l' (addition (multiplication n m) (addition n m')) : by rw [mul_comm m]
                    ... = multiplication l' (addition (addition (multiplication n m) n) m') : by rw [addition_assoc]
                    ... = multiplication l' (addition (addition (multiplication n m) m) n') : by rw [addition_assoc, addition_comm n, add_succ_commutes, ←addition_assoc]
                    ... = multiplication l' (addition (multiplication n' m) n') : by rw [mul_add_once]
                    ... = multiplication l' (multiplication n' m') : by refl
                    ... = multiplication l' (multiplication m' n') : by rw [mul_comm n']

        -- POW --

        def pow (m : ℕ) : ℕ → ℕ
        | 0 := 1
        | (n+1) := multiplication (pow n) m

        theorem pow_2_2_is_4 : pow 2 2 = 4 := rfl
        theorem pow_6_2_is_36 : pow 6 2 = 36 := rfl
        theorem pow_0_1_is_0 : pow 0 1 = 0 := rfl
        theorem pow_n_zero_is_1 (n : ℕ) : pow n 0 = 1 := rfl
        theorem pow_5_3_is_125 : pow 5 3 = 125 := rfl

        theorem pow_0_n_is_0 : ∀ n, (0 < n) → pow 0 n = 0
        | 0 hnpos := by { exact false.elim (lt_irrefl 0 hnpos) }
        | (n+1) _ :=
            calc
                pow 0 (n+1) = multiplication (pow 0 n) 0 : rfl
                    ... = 0 : by rw [mul_zero]

        theorem pow_n_1_is_n : ∀ m, (pow m 1) = m
        | 0 := rfl
        | (m+1) :=
            calc
                pow (m+1) 1 = multiplication (pow m 0) (m+1) : rfl
                    ... = multiplication 1 (m + 1) : rfl
                    ... = m+1 : by rw [one_mul]

        theorem pow_1_n_is_one : ∀ n, pow 1 n = 1
        | 0 := rfl
        | (n+1) :=
            calc
                pow 1 (n+1) = multiplication (pow 1 n) 1 : rfl
                    ... = multiplication 1 1 : by rw [pow_1_n_is_one]
                    ... = 1 : by rw mul_one

        theorem pow_addition_identity : ∀ (b m n : ℕ), pow b (addition m n) = multiplication (pow b m) (pow b n)
        | b 0 0 := by rw [add_zero, pow_n_zero_is_1, mul_one]
        | b (m+1) 0 :=
            calc
                pow b (addition (m+1) 0) = pow b (m+1) : by rw [add_zero]
                    ... = multiplication (pow b (m+1)) 1 : by rw [mul_one]
                    ... = multiplication (pow b (m+1)) (pow 0 0) : by rw [pow_n_zero_is_1]
        | b 0 (n+1) :=
            calc
                pow b (addition 0 (n+1)) = pow b (n+1) : by rw [zero_add]
                    ... = multiplication 1 (pow b (n+1)) : by rw [one_mul]
                    ... = multiplication (pow 0 0) (pow b (n+1)) : by rw [pow_n_zero_is_1]
        | b (m+1) (n+1) :=
            calc
                pow b (addition (m+1) (n+1)) = pow b (succ $ succ $ addition m n) : by rw [m_add_succ, addition_comm m, m_add_succ, addition_comm n]
                    ... = multiplication (multiplication (pow b (addition m n)) b) b : rfl
                    ... = multiplication (multiplication (multiplication (pow b m) (pow b n)) b) b : by rw [pow_addition_identity]
                    ... = multiplication (multiplication (multiplication (pow b m) b) (pow b n)) b : by rw [mul_comm (pow b m), mul_assoc (pow b n), mul_comm (pow b n)]
                    ... = multiplication (multiplication (pow b (m+1)) (pow b n)) b : by refl
                    ... = multiplication (pow b (m+1)) (multiplication (pow b n) b) : by rw [mul_assoc]
                    ... = multiplication (pow b (m+1)) (pow b (n+1)) : by refl

        theorem pow_multiplication_identity : ∀ (b c n : ℕ), pow (multiplication b c) n = multiplication (pow b n) (pow c n)
        | _ _ 0 := by rw [pow_n_zero_is_1, pow_n_zero_is_1, pow_n_zero_is_1, mul_one]
        | b c (n+1) :=
            calc
                pow (multiplication b c) (n+1) = multiplication (pow (multiplication b c) n) (multiplication b c) : rfl
                    ... = multiplication (multiplication (pow b n) (pow c n)) (multiplication b c) : by rw [pow_multiplication_identity]
                    ... = multiplication (pow b n) (multiplication (pow c n) (multiplication b c)) : by rw [mul_assoc]
                    ... = multiplication (pow b n) (multiplication (multiplication (pow c n) c) b) : by rw [mul_comm b, mul_assoc]
                    ... = multiplication (pow b n) (multiplication (pow c (n+1)) b) : by refl
                    ... = multiplication (multiplication (pow b n) b) (pow c (n+1)) : by rw [mul_comm (pow c (n+1)), mul_assoc]
                    ... = multiplication (pow b (n+1)) (pow c (n+1)) : rfl

        theorem pow_pow_identity : ∀ (b m n : ℕ), pow (pow b m) n = pow b (multiplication m n)
        | _ _ 0 := by rw [pow_n_zero_is_1, mul_zero, pow_n_zero_is_1]
        | _ 0 (n+1) := by rw [pow_n_zero_is_1, zero_mul, pow_n_zero_is_1, pow_1_n_is_one]
        | b (m+1) (n+1) :=
            let m' := m+1, n' := n+1 in
            calc
                pow (pow b m') (n+1) = multiplication (pow (pow b m') n) (pow b m') : rfl
                    ... = multiplication (pow b (multiplication m' n)) (pow b m') : by rw [pow_pow_identity]
                    ... = pow b (addition (multiplication m' n) m') : by rw [←pow_addition_identity]
                    ... = pow b (multiplication m' (succ n)) : rfl

    end hidden

end chap8ex2

namespace chap8ex3

    namespace hidden

        variable {α : Type}
        variables (a b c d : α)

        def append : list α → list α → list α
        | [] snd := snd
        | (hd::tl) snd := hd::(append tl snd)

        def reverse : list α → list α
        | [] := []
        | (hd::tl) := append (reverse tl) [hd]

        lemma reverse_abcd : reverse [a, b, c, d] = [d, c, b, a] := rfl

        def length : list α → ℕ
        | [] := 0
        | (hd::tl) := nat.succ $ length tl

        lemma length_abcd : length [a, b, c, d] = 4 := rfl

        def append_nil : ∀ (l: list α), append l [] = l
        | [] := rfl
        | (hd::tl) := calc append (hd::tl) [] = hd::(append tl []) : rfl
            ... = (hd::tl) : by rw [append_nil]

        def nil_append : ∀ (l : list α), append [] l = l
        | [] := rfl
        | (hd::tl) := rfl

        theorem append_lengths : ∀ (l m : list α), length (append l m) = length l + length m
        | l [] := calc
            length (append l []) = length l : by rw [append_nil]
                ... = length l + length [] : rfl
        | [] m := calc
            length (append [] m) = length m : by rw [nil_append]
                ... = 0 + length m : by rw [zero_add]
                ... = length [] + length m : rfl
        | (hd::tl) m :=
            calc length (append (hd::tl) m) = length (hd::(append tl m)) : rfl
                ... = length (append tl m) + 1 : rfl
                ... = (length tl + length m) + 1 : by rw [append_lengths]
                ... = (length tl + 1) + length m : by rw [add_assoc, add_comm (length m), add_assoc]
                ... = length (hd::tl) + length m : rfl

        theorem reverse_preserves_length : ∀ (l : list α), length l = length (reverse l)
        | [] := rfl
        | (hd::tl) := eq.symm $ calc
            length (reverse (hd::tl)) = length (append (reverse tl) [hd]) : rfl
                ... = length (reverse tl) + length [hd] : by rw [append_lengths]
                ... = length tl + 1 : by { rw [reverse_preserves_length tl], refl }
                ... = length (hd::tl) : rfl

    end hidden

end chap8ex3

namespace chap8ex4

    namespace hidden

        variable (C : ℕ → Type)

        #check @nat.rec C

        #check (@nat.below C : ℕ → Type)
        #check nat.below

        #reduce @nat.below C (3 : nat)

        #check (@nat.brec_on C :
        Π (n : ℕ), (Π (n' : ℕ), nat.below C n' → C n') → C n)

        def course_of_value_helper : Π (n : ℕ), (Π (n' : ℕ), nat.below C n' → C n') → nat.below C n
        | 0 _ := ()
        | (n+1) f := ⟨⟨f n (course_of_value_helper n f), course_of_value_helper n f⟩, ()⟩

        def course_of_value : Π (n : ℕ), (Π (n' : ℕ), nat.below C n' → C n') → C n
        | 0 := (
            λ h,
            have f : nat.below C 0 → C 0, from h 0,
            have nat.below C 0, from (),
            f this
        )
        | (n+1) := (
            λ h,
            have f : nat.below C (n+1) → C (n+1), from h (n+1),
            have nat.below C (n+1), from ⟨⟨course_of_value n h, course_of_value_helper C n h⟩, ()⟩,
            f this
        )

        def fib_impl : Π (n : ℕ), nat.below (λ (n : ℕ), ℕ) n → ℕ
        | 0 _ := 1
        | 1 _ := 1
        | (n+2) ⟨⟨fibn, ⟨⟨fibnplus1, _⟩, ()⟩⟩, ()⟩ := by { exact fibn + fibnplus1 }

        def fib : nat → nat := λ n,
        @course_of_value (λ (n : ℕ), ℕ) n (
            λ n' h,
            @fib_impl n' h
        )

        example : fib 0 = 1 := rfl
        example : fib 1 = 1 := rfl
        example : fib 2 = 2 := rfl
        example : fib 3 = 3 := rfl
        example : fib 4 = 5 := rfl
        example : fib 6 = 13 := rfl
    end hidden

    --- Well-founded recursion ---

    namespace hidden
        universes u v
        variable α : Sort u
        variable r : α → α → Prop
        variable h : well_founded r

        variable C : α → Sort (u + 1)
        variable F : Π x, (Π (y : α), r y x → C y) → C x

        #check @well_founded
        #check @acc

        #check (@well_founded.fix : Π {α : Sort u} {C : α → Sort (u + 1)} {r : α → α → Prop},
        well_founded r → (Π (x : α), (Π (y : α), r y x → C y) → C x) → Π (x : α), C x)

        def well_founded_fix (α : Sort u) (C : α → Sort (u + 1)):
        Π {r : α → α → Prop}, well_founded r
        → (Π (x : α), (Π (y : α), r y x → C y) → C x)
        → Π (x : α), C x
        | hr hwellfoundedr hc hx := (
            have hryxtocy : Π (y : α), hr y hx → C y, from (
                λ hy hryx,
                have hr hy hx, from hryx,
                have acc hr hy, from well_founded.apply hwellfoundedr hy,
                acc.rec_on this (λ hz _ f, hc hz f)
            ),
            hc hx hryxtocy
        )

        def f : Π (x : α), C x := well_founded.fix h F
        def f' : Π (x : α), C x := well_founded_fix α C h F
    end hidden

end chap8ex4

namespace chap8ex5

    universe u
    variable {α : Type u}

    inductive vector (α : Type u) : nat → Type u
    | nil {} : vector 0
    | cons   : Π {n}, α → vector n → vector (n+1)

    #print nat.no_confusion
    #print vector.no_confusion
    #check @eq.rec

    namespace vector
        local notation h :: t := cons h t
    end vector

    def vec_zero_plus_n : Π (n : ℕ), vector α n → vector α (0 + n) :=
    λ n v,
    have h1 : vector α (n + 0) := v,
    have h2 : vector α (n + 0) = vector α (0 + n), by rw [add_comm],
    eq.rec h1 h2

    -- With the equation compiler

    def append_with_equation_compiler : Π (m n : ℕ), vector α m → vector α n → vector α (m + n)
    | 0 n vector.nil v2 := vec_zero_plus_n n v2
    | (m+1) n (vector.cons hd tl) v2 := (
        have premise : vector α (m + n), from append_with_equation_compiler m n tl v2,
        have shuffle : vector α (m + n + 1) = vector α (m+1+n), by rw [add_assoc, add_comm n, add_assoc],
        eq.rec (vector.cons hd premise) shuffle
    )

    example : append_with_equation_compiler 2 1 (vector.cons 3 (vector.cons 5 vector.nil)) (vector.cons 8 vector.nil) = vector.cons 3 (vector.cons 5 (vector.cons 8 vector.nil)) := rfl

    -- Without the equation compiler

    def uncons : Π (n : ℕ), vector α (n + 1) → (α × vector α n)
    | _ (vector.cons hd tl) := prod.mk hd tl

    def uncons' : Π (m n : ℕ), vector α m → (m = n + 1) → (α × vector α n) :=
    λ m n v,
    @vector.cases_on
        _
        (λ {m} v, (m = n + 1) → (α × vector α n))
        _
        v
        (λ (h : 0 = n + 1), nat.no_confusion h)
        (λ {n': ℕ} (hd : α) (tl : vector α n'),
            assume (h : n' + 1 = n + 1),
            nat.no_confusion h (
                λ (h1 : n' = n),
                have vector α n' = vector α n, by rw [h1],
                prod.mk hd (eq.rec tl this)
            )
        )

    def append_with_elbow_grease : Π (m n : ℕ), vector α m → vector α n → vector α (m + n) :=
    λ m n v1 v2,
    (
        nat.rec_on m
        (show vector α 0 → vector α (0 + n), from λ _, vec_zero_plus_n n v2)
        (λ (m' : ℕ) (acc : vector α m' → vector α (m' + n)),
            show vector α (m' + 1) → vector α (m' + 1 + n), from (
                λ v,
                let ⟨hd, tl⟩ := uncons' _ m' v rfl in
                have newtl : vector α (m' + n), from acc tl,
                have newvec : vector α (m' + n + 1), from vector.cons hd newtl,
                have shuffle : vector α (m' + n + 1) = vector α ((m' + 1) + n), by rw [add_assoc, add_comm n, add_assoc],
                show vector α (m' + 1 + n), from eq.rec newvec shuffle
            )
        )
    ) v1

    example : append_with_elbow_grease 2 1 (vector.cons 3 (vector.cons 5 vector.nil)) (vector.cons 8 vector.nil) = vector.cons 3 (vector.cons 5 (vector.cons 8 vector.nil)) := rfl

end chap8ex5

namespace chap8ex6

    inductive aexpr : Type
    | const : ℕ → aexpr
    | var : ℕ → aexpr
    | plus : aexpr → aexpr → aexpr
    | times : aexpr → aexpr → aexpr

    open aexpr

    def sample_aexpr : aexpr :=
    plus (times (var 0) (const 7)) (times (const 2) (var 1))

    def aeval (v : ℕ → ℕ) : aexpr → ℕ
    | (const n)    := n
    | (var n)      := v n
    | (plus e₁ e₂)  := aeval e₁ + aeval e₂
    | (times e₁ e₂) := aeval e₁ * aeval e₂

    def sample_val : ℕ → ℕ
    | 0 := 5
    | 1 := 6
    | _ := 0

    -- Try it out. You should get 47 here.
    #eval aeval sample_val sample_aexpr

    def simp_const : aexpr → aexpr
    | (plus (const n₁) (const n₂))  := const (n₁ + n₂)
    | (times (const n₁) (const n₂)) := const (n₁ * n₂)
    | e                             := e

    theorem simp_const_eq' (v : ℕ → ℕ) :
    ∀ e : aexpr, aeval v (simp_const e) = aeval v e :=
    begin
        intros e,
        cases e,
        reflexivity,
        reflexivity,
        cases e_a,
            cases e_a_1,
            repeat { reflexivity },
        cases e_a,
            cases e_a_1,
            repeat { reflexivity },
    end

    theorem simp_const_eq (v : ℕ → ℕ) :
    ∀ e : aexpr, aeval v (simp_const e) = aeval v e
    | (const _) := rfl
    | (var _) := rfl
    | (plus e1 e2) := (
        match e1 with
            | (const m) := (
                match e2 with
                    | (const n) := (
                        have h : simp_const (plus (const m) (const n)) = const (m + n), from rfl,
                        calc
                            aeval v (simp_const (plus (const m) (const n))) = aeval v (const (m + n)) : by rw [h]
                                ... = aeval v (plus (const m) (const n)) : rfl
                    )
                    | (var n) := rfl
                    | (plus e1 e2) := rfl
                    | (times e1 e2) := rfl
                end
            )
            | (var n) := rfl
            | (plus _ _) := rfl
            | (times _ _) := rfl
        end
    )
    | (times e1 e2) := (
        match e1 with
            | (const m) := (
                match e2 with
                    | (const n) := (
                        have h : simp_const (times (const m) (const n)) = const (m * n), from rfl,
                        calc
                            aeval v (simp_const (times (const m) (const n))) = aeval v (const (m * n)) : by rw [h]
                                ... = aeval v (times (const m) (const n)) : rfl
                    )
                    | (var n) := rfl
                    | (plus e1 e2) := rfl
                    | (times e1 e2) := rfl
                end
            )
            | (var n) := rfl
            | (plus _ _) := rfl
            | (times _ _) := rfl
        end
    )

    def fuse : aexpr → aexpr
    | (plus (const n1) (const n2)) := simp_const (plus (const n1) (const n2))
    | (times (const n1) (const n2)) := simp_const (times (const n1) (const n2))
    | (plus e1 e2) := plus (fuse e1) (fuse e2)
    | (times e1 e2) := times (fuse e1) (fuse e2)
    | e := e

    -- This is very repetitive, but I think we need metaprogramming or custom
    -- tactics to address this.
    theorem fuse_eq (v : ℕ → ℕ) :
    ∀ e : aexpr, aeval v (fuse e) = aeval v e :=
    begin
        intro e,
        induction e,
            case const
            { reflexivity },
            case var
            { reflexivity },
            case plus : a b iha ihb
            { cases a,
              cases b,
                reflexivity,
                reflexivity,
                {
                    exact calc
                    aeval v (fuse (plus (const a) (plus b_a b_a_1))) = aeval v (plus (fuse (const a)) (fuse (plus b_a b_a_1))) : rfl
                        ... = aeval v (fuse (const a)) + aeval v (fuse (plus b_a b_a_1)) : rfl
                        ... = aeval v (const a) + aeval v (plus b_a b_a_1) : by rw [iha, ihb]
                        ... = aeval v (plus (const a) (plus b_a b_a_1)) : rfl
                },
                {
                    exact calc
                    aeval v (fuse (plus (const a) (times b_a b_a_1))) = aeval v (plus (fuse (const a)) (fuse (times b_a b_a_1))) : rfl
                        ... = aeval v (fuse (const a)) + aeval v (fuse (times b_a b_a_1)) : rfl
                        ... = aeval v (const a) + aeval v (times b_a b_a_1) : by rw [iha, ihb]
                        ... = aeval v (plus (const a) (times b_a b_a_1)) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (plus (var a) b)) = aeval v (plus (fuse (var a)) (fuse b)) : rfl
                            ... = aeval v (fuse (var a)) + aeval v (fuse b) : rfl
                            ... = aeval v (var a) + aeval v b : by rw [iha, ihb]
                            ... = aeval v (plus (var a) b) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (plus (plus a_a a_a_1) b)) = aeval v (plus (fuse (plus a_a a_a_1)) (fuse b)) : rfl
                            ... = aeval v (fuse (plus a_a a_a_1)) + aeval v (fuse b) : rfl
                            ... = aeval v (plus a_a a_a_1) + aeval v b : by rw [iha, ihb]
                            ... = aeval v (plus (plus a_a a_a_1) b) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (plus (times a_a a_a_1) b)) = aeval v (plus (fuse (times a_a a_a_1)) (fuse b)) : rfl
                            ... = aeval v (fuse (times a_a a_a_1)) + aeval v (fuse b) : rfl
                            ... = aeval v (times a_a a_a_1) + aeval v b : by rw [iha, ihb]
                            ... = aeval v (plus (times a_a a_a_1) b) : rfl
                },
            },
            case times : a b iha ihb
            { cases a,
              cases b,
                reflexivity,
                reflexivity,
                {
                    exact calc
                    aeval v (fuse (times (const a) (plus b_a b_a_1))) = aeval v (times (fuse (const a)) (fuse (plus b_a b_a_1))) : rfl
                        ... = aeval v (fuse (const a)) * aeval v (fuse (plus b_a b_a_1)) : rfl
                        ... = aeval v (const a) * aeval v (plus b_a b_a_1) : by rw [iha, ihb]
                        ... = aeval v (times (const a) (plus b_a b_a_1)) : rfl
                },
                {
                    exact calc
                    aeval v (fuse (times (const a) (times b_a b_a_1))) = aeval v (times (fuse (const a)) (fuse (times b_a b_a_1))) : rfl
                        ... = aeval v (fuse (const a)) * aeval v (fuse (times b_a b_a_1)) : rfl
                        ... = aeval v (const a) * aeval v (times b_a b_a_1) : by rw [iha, ihb]
                        ... = aeval v (times (const a) (times b_a b_a_1)) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (times (var a) b)) = aeval v (times (fuse (var a)) (fuse b)) : rfl
                            ... = aeval v (fuse (var a)) * aeval v (fuse b) : rfl
                            ... = aeval v (var a) * aeval v b : by rw [iha, ihb]
                            ... = aeval v (times (var a) b) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (times (plus a_a a_a_1) b)) = aeval v (times (fuse (plus a_a a_a_1)) (fuse b)) : rfl
                            ... = aeval v (fuse (plus a_a a_a_1)) * aeval v (fuse b) : rfl
                            ... = aeval v (plus a_a a_a_1) * aeval v b : by rw [iha, ihb]
                            ... = aeval v (times (plus a_a a_a_1) b) : rfl
                },
                {
                    exact calc
                        aeval v (fuse (times (times a_a a_a_1) b)) = aeval v (times (fuse (times a_a a_a_1)) (fuse b)) : rfl
                            ... = aeval v (fuse (times a_a a_a_1)) * aeval v (fuse b) : rfl
                            ... = aeval v (times a_a a_a_1) * aeval v b : by rw [iha, ihb]
                            ... = aeval v (times (times a_a a_a_1) b) : rfl
                },
            },
    end

end chap8ex6


import tutorial_world.incidenceplane --hide
open IncidencePlane --hide

/- Axiom :
incidence : P ≠ Q → P ∈ ℓ → Q ∈ ℓ → ℓ = line_through P Q
-/

/- Tactic : intro

## Summary:

`intro p` will turn a goal `⊢ P → Q` into a hypothesis `p : P`
and goal `⊢ Q`. If `P` and `Q` are sets `intro p` means "let $p$ be an arbitrary element of $P$".
If `P` and `Q` are propositions then `intro p` says "assume $P$ is true".

## Details

If your goal is a function or an implication `⊢ P → Q` then `intro`
will always make progress. `intro p` turns

`⊢ P → Q`

into

```
p : P
⊢ Q
```

The opposite tactic to intro is `revert`; given the situation
just above, `revert p` turns the goal back into `⊢ P → Q`.

**Variant:** Instead of calling `intro` multiple times, you can use
`intros`. That is, `intros h₁ h₂` is equivalent to `intro h₁, intro h₂`.

## Example

If your goal is an implication $P\implies Q$ then Lean writes
this as `⊢ P → Q`, and `intro p,` can be thought of as meaning
"let $p$ be a proof of $P$", or more informally "let's assume that
$P$ is true". The goal changes to `⊢ Q` and the hypothesis `p : P`
appears in the local context.

-/

/-
This level introduces the `intros` tactic. This allows you to introduce
a new hypothesis in the context. You can learn more about it in the side bar.
-/

variables {Ω : Type} [IncidencePlane Ω] --hide
variables {A B : Ω} {r s : Line Ω} -- hide

/- Lemma :
If two lines contain two distinct points, then they are the same
-/
lemma equal_lines_of_contain_two_points :
A ≠ B → A ∈ r →  A ∈ s → B ∈ r → B ∈ s → 	r = s :=
begin
  intros hAB hAr hAs hBr hBs,
  rw incidence hAB hAr hBr,
  rw incidence hAB hAs hBs,

end


import SciLean.Core.Functions
import SciLean.Tactic.AutoDiff.Main

namespace SciLean.Smooth

variable {α β γ : Type}
variable {X Y Z W : Type} [Vec X] [Vec Y] [Vec Z] [Vec W]
variable {Y₁ Y₂ : Type} [Vec Y₁] [Vec Y₂]

set_option maxHeartbeats 900
set_option synthInstance.maxHeartbeats 300
set_option synthInstance.maxSize 50

--- Test 0

-- We want to solve all these in a single pass i.e. linear complexity in the expression size
-- macro "diff_simp" : tactic => `(tactic| simp) -- `(autodiff_core (config := {singlePas
-- s := true}))

-- I
example : ∂ (λ x : X => x) = λ x dx => dx := by autodiff; done

-- K
example : ∂ (λ (x : X) (y : Y) => x) = λ x dx y => dx := by autodiff; done
example (x : X) : ∂ (λ (y : Y) => x) = λ y dy => (0:X) := by autodiff; done

-- B
example
  : ∂ (λ (f : Y → Z) (g : X → Y) (x : X) => f (g x))
    =
    λ f df g x => df (g x) := by autodiff; done
example (f : Y → Z) [IsSmooth f]
  : ∂ (λ (g : X → Y) (x : X) => f (g x))
    =
    λ g dg x => ∂ f (g x) (dg x) := by autodiff; done
example (f : Y → Z) [IsSmooth f]
  (g : X → Y) [IsSmooth g]
  : ∂ (λ (x : X) => f (g x))
    =
    λ x dx => ∂ f (g x) (∂ g x dx) := by autodiff; done

-- C
-- set_option trace.Meta.synthInstance true in
-- set_option trace.Meta.Tactic.simp true in
-- set_option trace.Meta.Tactic.simp.unify false in
example
  : ∂ (λ (f : X → Y → Z) (y : Y) (x : X) => f x y)
    =
    λ f df y x => df x y := by autodiff; done
example (f : X → Y → Z) [∀ x, IsSmooth (f x)]
  : ∂ (λ (y : Y) (x : X) => f x y)
    =
    λ y dy x => ∂ (f x) y dy := by autodiff; done
example (f : X → Y → Z) [IsSmooth f] (y : Y)
  : ∂ (λ (x : X) => f x y)
    =
    λ x dx => ∂ f x dx y := by autodiff; done

-- S
-- set_option trace.Meta.Tactic.simp true in
example
  : ∂ (λ (f : X → Y → Z) (g : X → Y) (x : X) => f x (g x))
    =
    λ f df g x => df x (g x) := by autodiff; done
example (f : X → Y → Z) [∀ x, IsSmooth (f x)]
  : ∂ (λ (g : X → Y) (x : X) => f x (g x))
    =
    λ g dg x => ∂ (f x) (g x) (dg x) := by autodiff; done
example (f : X → Y → Z) [IsSmooth f] [∀ x, IsSmooth (f x)]
  (g : X → Y) [IsSmooth g]
  : ∂ (λ (x : X) => f x (g x))
    =
    λ x dx => ∂ f x dx (g x) + ∂ (f x) (g x) (∂ g x dx) := by autodiff; done

-- diff_of_diag
example
  (f : Y₁ → Y₂ → Z) [IsSmooth f] [∀ y₁, IsSmooth (f y₁)]
  (g₁ : X → Y₁) [IsSmooth g₁]
  (g₂ : X → Y₂) [IsSmooth g₂]
  : ∂ (λ x => f (g₁ x) (g₂ x))
    =
    λ x dx => ∂ f (g₁ x) (∂ g₁ x dx) (g₂ x) +
              ∂ (f (g₁ x)) (g₂ x) (∂ g₂ x dx) := by autodiff; done

-- diff_of_parm
example
  (f : X → α → Y) [IsSmooth f]
  (a : α)
  : ∂ (λ x => f x a) = λ x dx => ∂ f x dx a := by autodiff; done


-- ----------------
-- Demostrar que
--    s ∩ t = t ∩ s
-- ----------------

import data.set.basic
open set

variable {α : Type}
variables s t u : set α

example : s ∩ t = t ∩ s :=
sorry


/-
Copyright (c) 2022 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics
import data.real.basic -- imports the real numbers
import solutions.section02reals.sheet3 -- import the definition of `tendsto` from a previous sheet

-- you can maybe do this one now
theorem tendsto_neg {a : ℕ → ℝ} {t : ℝ} (ha : tendsto a t) :
  tendsto (λ n, - a n) (-t) :=
begin
  sorry,
end

/-
`tendsto_add` is quite a challenge. In a few weeks' time I'll
show you a two-line proof using filters, but right now
as you're still learning I think it's important that you
try and suffer and struggle through the first principles proof.
BIG piece of advice: write down a complete maths proof first,
with all the details there. Then, once you know the maths
proof, try translating it into Lean. Note that a bunch
of the results we proved in sheet 4 will be helpful.
-/

/-- If `a(n)` tends to `t` and `b(n)` tends to `u` then `a(n) + b(n)`
tends to `t + u`. -/
theorem tendsto_add {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tendsto a t) (hb : tendsto b u) :
  tendsto (λ n, a n + b n) (t + u) :=
begin
  sorry
end

/-- If `a(n)` tends to t and `b(n)` tends to `u` then `a(n) - b(n)`
tends to `t - u`. -/
theorem tendsto_sub {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tendsto a t) (hb : tendsto b u) :
  tendsto (λ n, a n - b n) (t - u) :=
begin
  -- this one follows without too much trouble from earlier results.
  sorry
end


/-
This file defines the at-most-k Boolean cardinality constraint.

Authors: Cayden Codel, Jeremy Avigad, Marijn Heule
Carnegie Mellon University
-/

import data.list.basic
import cnf.literal
import cnf.assignment
import cnf.clause
import cardinality.distinct
import cnf.encoding

variables {V : Type*} [decidable_eq V] [inhabited V]

open assignment
open clause
open list
open nat
open distinct

def amk (k : nat) : constraint := λ (l : list bool), l.count tt ≤ k

namespace amk

@[simp] theorem amk_nil (k : nat) : amk k [] = tt := rfl

@[simp] theorem amk_singleton_pos (k : nat) (b : bool) : amk (k + 1) [b] = tt :=
by { cases b; simp [amk, count_singleton'] }

variables (k : nat) (τ : assignment V) (l : list (literal V)) (lit : literal V)

@[simp] theorem eval_nil : (amk k).eval τ [] = tt :=
by simp only [constraint.eval, amk, count_nil, to_bool_true_eq_tt, zero_le, map_nil]

@[simp] theorem eval_singleton_pos : (amk (k + 1)).eval τ [lit] = tt :=
by { cases h : lit.eval τ; simp [constraint.eval, amk, count_singleton', h] }

@[simp] theorem eval_singleton_zero : ((amk 0).eval τ [lit] = tt) ↔ lit.eval τ = ff :=
begin
  cases h : (lit.eval τ),
  { split,
    { tautology },
    { intro _, simp [constraint.eval, amk, h] } },
  { split,
    { intro hamk,
      simp [constraint.eval, amk, h] at hamk,
      contradiction },
    { intro h, contradiction } }
end

theorem eval_tt_of_ge_length {k : nat} {l : list (literal V)} :
  k ≥ length l → ∀ (τ : assignment V), (amk k).eval τ l = tt :=
begin
  intros hk τ,
  simp [constraint.eval, amk],
  have := count_le_length tt (map (literal.eval τ) l),
  rw length_map at this,
  exact le_trans this hk
end

theorem eval_cons_pos {k : nat} {τ : assignment V} {lit : literal V} :
  lit.eval τ = tt → ∀ l, (amk (k + 1)).eval τ (lit :: l) = (amk k).eval τ l :=
assume hlit l, by simp [constraint.eval, amk, hlit, succ_le_succ_iff]

theorem eval_cons_neg {τ : assignment V} {lit : literal V} :
  lit.eval τ = ff → ∀ k l, (amk k).eval τ (lit :: l) = (amk k).eval τ l :=
assume hlit l, by simp [constraint.eval, amk, hlit]

theorem eval_tt_of_le_of_eval_tt {τ : assignment V} {l : list (literal V)}
  {k₁ k₂ : nat} : k₁ ≤ k₂ → (amk k₁).eval τ l = tt → (amk k₂).eval τ l = tt :=
begin
  simp only [constraint.eval, amk, ge_iff_le, to_bool_iff],
  intros hk h₁,
  exact le_trans h₁ hk
end

theorem eval_sublist {k : nat} {τ : assignment V} {l₁ l₂ : list (literal V)} :
  l₁ <+ l₂ → (amk k).eval τ l₂ = tt → (amk k).eval τ l₁ = tt :=
begin
  simp [constraint.eval, amk],
  intros hs h,
  exact le_trans (sublist.count_le (sublist.map (literal.eval τ) hs) tt) h
end

theorem eval_drop {k : nat} {τ : assignment V} {l : list (literal V)} :
  (amk k).eval τ l = tt → ∀ (i : nat), (amk k).eval τ (l.drop i) = tt :=
assume hamk i, eval_sublist (drop_sublist i l) hamk

theorem eval_take {k : nat} {τ : assignment V} {l : list (literal V)} :
  (amk k).eval τ l = tt → ∀ (i : nat), (amk k).eval τ (l.take i) = tt :=
assume hamk i, eval_sublist (take_sublist i l) hamk

theorem eval_take_tail_pos {τ : assignment V} {l : list (literal V)} {i : nat} {Hi : i < length l} :
  (l.nth_le i Hi).eval τ = tt → ∀ (k : nat),
  (amk (k + 1)).eval τ (l.take (i + 1)) = (amk k).eval τ (l.take i) :=
begin
  intros hl k,
  induction i with i ih generalizing l k,
  { rw nth_le_zero at hl,
    simp [take_one_of_ne_nil (ne_nil_of_length_pos Hi), hl, constraint.eval, amk] },
  { rcases exists_cons_of_ne_nil (ne_nil_of_length_pos (pos_of_gt Hi)) with ⟨l₁, ls, rfl⟩,
    rw nth_le at hl,
    cases h₁ : l₁.eval τ,
    { simp only [take, eval_cons_neg h₁, ih _ hl] },
    { cases k,
      { simp [constraint.eval, amk, h₁, succ_le_succ_iff],
        simp at Hi,
        have Hi' := (succ_lt_succ_iff.mp Hi),
        use [ls.nth_le _ Hi', nth_le_mem_take_of_lt Hi' (lt_succ_self i), hl] },
      { simp [take, eval_cons_pos h₁], exact ih _ hl } } }
end

theorem eval_take_tail_neg {τ : assignment V} {l : list (literal V)} {i : nat} {Hi : i < length l} :
  (l.nth_le i Hi).eval τ = ff → ∀ (k : nat),
  (amk k).eval τ (l.take (i + 1)) = (amk k).eval τ (l.take i) :=
begin
  intros hl k,
  induction i with i ih generalizing l k,
  { rw nth_le_zero at hl,
    simp [take_one_of_ne_nil (ne_nil_of_length_pos Hi), hl, constraint.eval, amk] },
  { rcases exists_cons_of_ne_nil (ne_nil_of_length_pos (pos_of_gt Hi)) with ⟨l₁, ls, rfl⟩,
    rw nth_le at hl,
    cases h₁ : l₁.eval τ,
    { simp [take, eval_cons_neg h₁, ih _ hl] },
    { cases k,
      { simp [take, constraint.eval, amk, h₁] },
      { simp [take, eval_cons_pos h₁, ih _ hl] } } }
end

theorem eval_tt_of_sublist_of_eval_tt {k : nat} {τ : assignment V} {l₁ l₂ : list (literal V)} :
  l₁ <+ l₂ → (amk k).eval τ l₂ = tt → (amk k).eval τ l₁ = tt :=
begin
  simp [constraint.eval, amk],
  intros hls h₁,
  exact le_trans (sublist.count_le (sublist.map (literal.eval τ) hls) tt) h₁
end

theorem eval_take_succ_tt_of_eval_take_tt {i k : nat} :
  (amk k).eval τ (l.take (i + 1)) = tt → (amk k).eval τ (l.take i) = tt :=
λ h, eval_tt_of_sublist_of_eval_tt (take_sublist_of_le (le_succ i) l) h

/-! # amz -/

theorem amz_of_amz_cons {τ : assignment V} {l : list (literal V)} {lit : literal V} :
  (amk 0).eval τ (lit :: l) = tt → (amk 0).eval τ l = tt :=
begin
  simp [constraint.eval, amk], cases literal.eval τ lit; simp
end

-- The special case where k = 0 is handled
theorem amz_eval_tt_iff_forall_eval_ff {τ} {l} :
  (amk 0).eval τ l = tt ↔ (∀ ⦃lit : literal V⦄, lit ∈ l → lit.eval τ = ff) :=
begin
  split,
  { simp [constraint.eval, amk] },
  { intro h,
    rw [constraint.eval, amk, to_bool_iff, le_zero_iff],
    apply count_eq_zero_of_not_mem,
    simpa }
end

-- Can be done with contrapose, somehow
theorem amz_eval_ff_iff_exists_eval_tt :
  (amk 0).eval τ l = ff ↔ (∃ (lit : literal V), lit ∈ l ∧ lit.eval τ = tt) :=
begin
  split,
  { contrapose, simp, exact amz_eval_tt_iff_forall_eval_ff.mpr },
  { contrapose, simp, exact amz_eval_tt_iff_forall_eval_ff.mp }
end

theorem eval_cons_pos_zero {τ : assignment V} {lit : literal V} :
  lit.eval τ = tt → ∀ l, (amk 0).eval τ (lit :: l) = ff :=
begin
  intros hlit l,
  apply (amz_eval_ff_iff_exists_eval_tt τ (lit :: l)).mpr,
  use [lit, mem_cons_self _ _, hlit]
end

-- Can probably be shortened with the correct order of cases
theorem exists_amk_split {k : nat} {τ : assignment V} {l : list (literal V)} :
  (amk k).eval τ l = tt → ∀ {k₁ k₂ : nat}, k₁ + k₂ = k →
  ∃ {l₁ l₂ : list (literal V)}, l₁ ++ l₂ = l ∧
  (amk k₁).eval τ l₁ = tt ∧ (amk k₂).eval τ l₂ = tt :=
begin
  intros hamk k₁ k₂ hks,
  induction l with lit₁ ls ih generalizing k k₁ k₂,
  { use [[], []],
    simp },
  { cases k,
    { cases hlit₁ : (literal.eval τ lit₁),
      { rw eval_cons_neg hlit₁ at hamk,
        rcases ih hamk hks with ⟨l₁, l₂, hls, hl₁, hl₂⟩,
        use [(lit₁ :: l₁), l₂],
        simp [hls, eval_cons_neg hlit₁, hl₁, hl₂] },
      { rw [amz_eval_tt_iff_forall_eval_ff] at hamk,
        rw (hamk (mem_cons_self lit₁ ls)) at hlit₁,
        contradiction } },
    { cases k₁,
      { rw zero_add at hks, subst hks,
        use [[], lit₁ :: ls],
        simpa },
      { cases hlit₁ : (literal.eval τ lit₁),
        { rw eval_cons_neg hlit₁ at hamk,
          rcases ih hamk hks with ⟨l₁, l₂, hls, hl₁, hl₂⟩,
          use [(lit₁ :: l₁), l₂],
          simp [hls, eval_cons_neg hlit₁, hl₁, hl₂] },
        { rw succ_add at hks,
          rw eval_cons_pos hlit₁ at hamk,
          rcases ih hamk (succ.inj hks) with ⟨l₁, l₂, hls, hl₁, hl₂⟩,
          use [(lit₁ :: l₁), l₂],
          simp [hls, eval_cons_pos hlit₁, hl₁, hl₂] } } } }
end

theorem eval_eq_of_agree_on {τ₁ τ₂ : assignment V} {l : list (literal V)} :
  ∀ (k : nat), (agree_on τ₁ τ₂ (clause.vars l)) → (amk k).eval τ₁ l = (amk k).eval τ₂ l :=
begin
  intros k hagree_on,
  induction l with l ls ih generalizing k,
  { simp only [eval_nil] },
  { have := eval_eq_of_agree_on_of_var_mem hagree_on (mem_vars_of_mem (mem_cons_self l ls)),
    cases h : (l.eval τ₁),
    { rw eval_cons_neg h,
      rw this at h,
      rw eval_cons_neg h,
      exact ih (agree_on_subset (vars_subset_of_vars_cons _ _) hagree_on) k },
    { cases k,
      { rw eval_cons_pos_zero h,
        rw this at h,
        rw eval_cons_pos_zero h },
      { rw eval_cons_pos h,
        rw this at h,
        rw eval_cons_pos h,
        exact ih (agree_on_subset (vars_subset_of_vars_cons _ _) hagree_on) k } } }
end

/-! # amo -/

theorem amo_eval_tt_iff_distinct_eval_ff_of_eval_tt
  {τ : assignment V} {l : list (literal V)} :
  (amk 1).eval τ l = tt ↔ (∀ {lit₁ lit₂ : literal V},
  distinct lit₁ lit₂ l → lit₁.eval τ = tt → lit₂.eval τ = ff) :=
begin
  induction l with l₁ ls ih,
  { split,
    { intros _ lit₁ lit₂ hdis,
      exact absurd hdis (not_distinct_nil _ _) },
    { rw eval_nil, tautology } },
  { cases ls with l₂ ls,
    { split,
      { intros _ lit₁ lit₂ hdis,
        exact absurd hdis (not_distinct_singleton _ _ _) },
      { rw eval_singleton_pos, tautology } },
    { split,
      { intros heval lit₁ lit₂ hdis h₁,
        have hmem₂ := mem_tail_of_distinct_cons hdis,
        rcases hdis with ⟨i, j, hi, hj, hij, hil, hjl⟩,
        cases i,
        { rw nth_le at hil,
          rw [hil, eval_cons_pos h₁, amz_eval_tt_iff_forall_eval_ff] at heval,
          exact heval hmem₂ },
        { cases j,
          { linarith },
          { have : distinct lit₁ lit₂ (l₂ :: ls),
            { rw [length, succ_lt_succ_iff] at hi hj,
              rw succ_lt_succ_iff at hij,
              rw nth_le at hil hjl,
              exact ⟨i, j, hi, hj, hij, hil, hjl⟩ },
            cases h : (literal.eval τ l₁),
            { rw eval_cons_neg h at heval,
              exact ih.mp heval this h₁ },
            { rw [eval_cons_pos h, amz_eval_tt_iff_forall_eval_ff] at heval,
              exact heval hmem₂ } } } },
      { intro h,
        cases h₁ : (literal.eval τ l₁),
        { rw eval_cons_neg h₁,
          apply ih.mpr,
          intros lit₁ lit₂ hdis' h₁',
          exact h (distinct_cons_of_distinct l₁ hdis') h₁' },
        { rw [eval_cons_pos h₁, amz_eval_tt_iff_forall_eval_ff],
          intros x hx,
          exact h (distinct_cons_of_mem l₁ hx) h₁ } } } }
end

end amk