import Mathlib.Tactic.Basic

/-!
## Tactic Mode

In Chapter 1, whenever a proof was required, we simply put a `sorry` placeholder.
For a tactical proof, we will now write `by` to enter tactic mode. In this mode,
we can apply a sequence of tactics, separated by semicolons or new lines.

Tactics operate on the goal, which consists of the proposition `Q` that we want to
prove and of a local context `C`. The local context consists of variable declarations
of the form `c : τ` and hypotheses of the form `h : P`. We write `C ⊢ Q` to denote a
goal, where `C` is a list of variables and hypotheses and `Q` is the goal’s target.

To make things more concrete, consider the following Lean example:

-/

lemma fst_of_two_props :
  ∀ a b : Prop, a → b → a := by
  intros a b
  intros ha hb
  apply ha

/-!
Note that the implication arrow `→` is right-associative; this means that `a → b → a`
is the same as `a → (b → a)`. Intuitively speaking, it has the meaning “`a` implies
that `b` implies `a`,” or equivalently “`a` and `b` imply `a`.” In the example, three tactics
are invoked, each on its own line. Let us trace their behavior:

1. Initially, the goal is simply the lemma statement:
    ```lean
    ⊢ ∀ a b : Prop, a → b → a
    ```

2.  The `intros a b` tactic tells Lean to fix two free variables, `a` and `b`,
corresponding to the two bound variables of the same names. Offen, we name the free
variables after the bound variables. The tactic mimics how mathematicians
work on paper: To prove a ∀-quantified proposition, it suffices to prove it for
some arbitrary but fixed value of the bound variable. The goal becomes
    ```lean
    a b : Prop ⊢ a → b → a
    ```

3. The `intros ha hb` tactic tells Lean to move the assumptions `a` and `b` to the
local context and to call these hypotheses `ha` and `hb`. Indeed, to prove an
implication, it suffices to take its left-hand side as hypothesis and prove its
right-hand side. The goal becomes
    ```lean
    a b : Prop, ha : a, hb : b ⊢ a
    ```

4. The `apply ha` tactic tells Lean to match the hypothesis `a`, called `ha`, against
the goal `⊢ a`. Since `a` is syntactically equal to `a`, we have a match, and this
completes the proof.

Informally, in a style reminiscent of pen-and-paper mathematics, we could
write the proof as follows:

- Let `a` and `b` be propositions.
- Assume `(ha) a` and `(hb) b` are true.
- To prove `a`, we use hypothesis `ha`.

(Mathematicians would probably use numeric tags such as (1) and (2) for the hypotheses
instead of informative names.)

Going back to the Lean proof, we can avoid the `intros` invocations by declaring
the variables and hypotheses as parameters of the lemma, as follows:
-/
lemma fst_of_two_props₂ (a b : Prop) (ha : a) (hb : b) :
  a := by
  apply ha
/-!
Here is an example with multiple `apply`s in sequence:
-/
lemma prop_comp (a b c : Prop) (hab : a → b) (hbc : b → c) :
  a → c := by
  intro ha
  apply hbc
  apply hab
  apply ha
/-!
Putting on our mathematician’s hat, we can verbalize the last proof as follows:

- Assume `(ha) a` is true.
- To prove `c`, by hypothesis `hbc` it suffices to prove `b`.
- To prove `b`, by hypothesis `hab` it suffices to prove `a`.
- To prove `a`, we use hypothesis `ha`.
-/


import .src_10_false_and_negation

variables p q r : Prop

namespace mth1001

section lem

/-
The law of the excluded middle asserts `p ∨ ¬p`, for any proposition `p`. Together with
or elimination, this leads to a proof method called 'proof by cases' or 'proof by exhaustion'.

Suppose we wish to prove `q`. By the law of the excluded middle, we have `p ∨ ¬p` (for any `p`).
If we can prove both `p → q` and `¬p → q`, then, by or elimination, we have a proof of `q`.

In summary, to prove `q`, it suffices to show both `p → q` and `¬p → q`.
-/

/-
This pattern of reasoning is caputured by the tactic `by_cases`. If `p` is a propostion and if `q`
is the current goal, then `by_cases k : p` replaces the goal `q` with two subgoals, both to solve
`q`, but with `k : p` in the  context of the first subgoal and `k : ¬p` in the context of the
second. There is nothing special here about the names `p`, `q`, or `k`.
-/

/-
To use `by_cases`, we first open the `classical` namespace and then
instruct Lean to treat all propostions as 'decidable'. You don't need to know what this means.
-/
open classical
local attribute [instance] prop_decidable

example (h₁ : p → q) (h₂ : ¬p → q) : q :=
begin
  by_cases k : p,
  { exact h₁ k, },
  { exact h₂ k, },
end

/-
The following term-style proof explicitly uses or elimnation and the law of the excluded middle.
Here, `classical.em p` is a proof of `p ∨ ¬p`.
-/
example (h₁ : p → q) (h₂ : ¬p → q) : q :=
or.elim (classical.em p) h₁ h₂

-- Exercise 063:
-- Complete the following proof of one direction of one of De Morgan's laws.
example : ¬(p ∧ q) → ¬p ∨ ¬q :=
begin
  intro h,
  by_cases k : p,
  { right,
    intro hq,
    exact h ⟨k, hq⟩, },
  { sorry, },
end

-- Exercise 064:
-- We'll now show one can deduce `¬p ∨ q` from `p → q`.
example : (p → q) → (¬p ∨ q) :=
begin
  sorry
end

end lem

section reductio

/-
Proof by contradiction is another method of reasoning closely associated with the
law of the excluded middle.

If, on the assumption of `¬p`, one can derive `false`, then one has `p`.
-/

open classical
local attribute [instance] prop_decidable

/-
If the current goal is to prove `p`, the `by_contradiction` tactic introduces a new premise
`a : ¬p` into the context and replaces the goal with `false`.

Below, we invoke the tactic as `by_contradiction k`. With this variant, we specify the name
of the new premise to be `k`, thereby introducing `k : ¬p` into the context.
-/
example (h : ¬p → false) : p :=
begin
  by_contradiction k,
  exact h k,
end

-- Exercise 065:
-- Using proof by contradiction, we have the remaining part of the law of double negation.
theorem self_of_not_not : ¬¬p → p :=
begin
  intro hnnp,
  by_contradiction hnp,
  sorry
end

-- Exercise 066:
-- Combine our two previous results to show `¬¬p ↔ p`.
theorem not_not {p : Prop} : ¬¬p ↔ p :=
begin
  sorry
end

-- Exercise 067:
-- We show the remaining part of the equivalence of `p → q` with its contrapositive.
example : (¬q → ¬p) → (p → q) :=
begin
  sorry
end

-- Exercise 068:
/-
We can prove one direction of one of of De Morgan's laws using proof by contradiction.
Complete the following proof using only the `intro`, `by_contradiction`, `apply`, `split`,
`exact`, `left`, and `right` tacitcs. You may need to use `by_contradiction` more than once.
 -/
theorem not_or_not_of_not_and : ¬(p ∧ q) → ¬p ∨ ¬q :=
begin
  sorry
end

end reductio

/-
SUMMARY:

* The law of the excluded middle: `p ∨ ¬p` holds for every `p`.
* Proof `by_contradiction`. To prove `p`, we assume `¬p` and derive `false`.
-/

end mth1001


/-
Take funtion as argument and use it in implementation.
-/

def apply_nat_to_nat (f : ℕ → ℕ) (n : ℕ) : ℕ :=
    f n

#eval apply_nat_to_nat nat.succ 1
#eval apply_nat_to_nat nat.pred 1

/-
Make idea completely general using polymorphism
-/

def apply {α β : Type} (f : α → β) (a : α) : β :=
    f a

#eval apply nat.succ 1
#eval apply nat.pred 1
#eval apply string.length "I love logic!"

/-
Return function as a result
-/

def apply_twice {α : Type} (f : α → α ) : α → α :=
    λ a, f (f a)

#reduce (apply_twice nat.succ)
#reduce apply_twice nat.pred

def double (n : ℕ) := 2 * n

def square (n : ℕ) := n ^ 2

#eval apply_twice nat.succ 3    -- application is left associative
#eval apply_twice nat.pred 3
#eval (apply_twice double) 3

def square_twice := apply_twice square
def double_twice := apply_twice double

#eval square_twice 5

/-
That's composition of a function with itself,
but we can also compose different functions.
Here's a special case.
-/

def compose_1 {α : Type} (g : α → α ) (f : α → α ): α → α :=
    λ a, g (f a)

def double_inc := compose_1 double nat.succ
#reduce double_inc
#eval double_inc 3

-- Define and try out inc_double (first double then increment)

def is_even (n : ℕ) : bool := n % 2 = 0

#eval is_even 6

def compose {α β γ : Type} (g : β → γ) (f : α → β) : α → γ :=
    λ (a : α), g (f a)

def even_length := compose is_even string.length
#eval even_length "I love logic!!!!!!!"

def even_length' := is_even ∘ string.length     -- math notation

/-
Functions are objects, too, and there are associated
operations that apply to functions. Composition is one
such operation. Differentiation is another example of
a function of functions.
-/

/-
Exercise: apply_n
-/

/-
List map.
-/

def list_map {α β : Type} : (α → β) → list α → list β
| f [] := []
| f (h :: t) := list.cons (f h) (list_map f t)

-- exercise box_map
-- exercise option_map
-- exercise tree_map


/-
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

/-!

# Logic in Lean, example sheet 6 : "or" (∨`)

We learn about how to manipulate `P ∨ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics

* `left` and `right`
* `cases` (new functionality)

-/

-- Throughout this sheet, `P`, `Q`, `R` and `S` will denote propositions.

variables (P Q R S : Prop)

example : P → P ∨ Q :=
begin
  sorry
end

example : Q → P ∨ Q :=
begin
  sorry,
end

example : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  sorry
end

-- symmetry of `or`
example : P ∨ Q → Q ∨ P :=
begin
  sorry
end

-- associativity of `or`
example : (P ∨ Q) ∨ R ↔ P ∨ (Q ∨ R) :=
begin
  sorry,
end

example : (P → R) → (Q → S) → P ∨ Q → R ∨ S :=
begin
  sorry,
end

example : (P → Q) → P ∨ R → Q ∨ R :=
begin
  sorry,
end

example : (P ↔ R) → (Q ↔ S) → (P ∨ Q ↔ R ∨ S) :=
begin
  sorry,
end

-- de Morgan's laws
example : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
begin
  sorry
end

example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  sorry
end


/-
Copyright (c) 2020 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou, Yury Kudryashov
-/
import analysis.normed.group.basic
import algebra.indicator_function

/-!
# Indicator function and norm

This file contains a few simple lemmas about `set.indicator` and `norm`.

## Tags
indicator, norm
-/

variables {α E : Type*} [normed_group E] {s t : set α} (f : α → E) (a : α)

open set

lemma norm_indicator_eq_indicator_norm :
  ∥indicator s f a∥ = indicator s (λa, ∥f a∥) a :=
flip congr_fun a (indicator_comp_of_zero norm_zero).symm

lemma nnnorm_indicator_eq_indicator_nnnorm :
  nnnorm (indicator s f a) = indicator s (λa, nnnorm (f a)) a :=
flip congr_fun a (indicator_comp_of_zero nnnorm_zero).symm

lemma norm_indicator_le_of_subset (h : s ⊆ t) (f : α → E) (a : α) :
  ∥indicator s f a∥ ≤ ∥indicator t f a∥ :=
begin
  simp only [norm_indicator_eq_indicator_norm],
  exact indicator_le_indicator_of_subset ‹_› (λ _, norm_nonneg _) _
end

lemma indicator_norm_le_norm_self : indicator s (λa, ∥f a∥) a ≤ ∥f a∥ :=
indicator_le_self' (λ _ _, norm_nonneg _) a

lemma norm_indicator_le_norm_self : ∥indicator s f a∥ ≤ ∥f a∥ :=
by { rw norm_indicator_eq_indicator_norm, apply indicator_norm_le_norm_self }


open classical

theorem de_morgan {p q : Prop} : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  iff.intro
    (assume h : ¬(p ∨ q),
      and.intro
        (assume hp : p,
          have hpq : p ∨ q, from or.inl hp,
          absurd hpq h)
        (assume hq : q,
          have hpq : p ∨ q, from or.inr hq,
          absurd hpq h))
    (assume h : ¬p ∧ ¬q,
      have hnp : ¬p, from h.left,
      have hnq : ¬q, from h.right,
      assume hpq : p ∨ q,
      hpq.elim
        (assume hp : p,
          absurd hp hnp)
        (assume hq : q,
          absurd hq hnq))

-- BEGIN
theorem dne {p : Prop} (h : ¬¬p) : p :=
or.elim (em p)
  (assume hp : p, hp)
  (assume hnp : ¬p, absurd hnp h)
-- END

theorem em' (p : Prop) : p ∨ ¬p :=
have hnnponp : ¬¬(p ∨ ¬p), from (
    assume hnponp : ¬(p ∨ ¬p),
    have hnpahnnp : ¬p ∧ ¬¬p, from de_morgan.elim_left hnponp,
    have hnp : ¬p, from and.elim_left hnpahnnp,
    have hp : p, from dne (and.elim_right hnpahnnp),
    absurd hp hnp
),
show p ∨ ¬p, from dne hnnponp

theorem em'' (p: Prop) : p ∨ ¬p :=
suffices hdnem : ¬¬(p ∨ ¬p), from dne hdnem,
show ¬¬(p ∨ ¬p), from (
  assume hnem : ¬(p ∨ ¬p),
  have false : ¬p ∧ ¬¬p, from de_morgan.elim_left hnem,
  absurd (and.elim_left false) (and.elim_right false)
)


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

! This file was ported from Lean 3 source module order.monotone.odd
! leanprover-community/mathlib commit 9116dd6709f303dcf781632e15fdef382b0fc579
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Order.Monotone.Union
import Mathlib.Algebra.Order.Group.Instances

/-!
# Monotonicity of odd functions

An odd function on a linear ordered additive commutative group `G` is monotone on the whole group
provided that is is monotone on `Set.Ici 0`, see `monotone_of_odd_of_monotoneOn_nonneg`. We also
prove versions of this lemma for `Antitone`, `StrictMono`, and `StrictAnti`.
-/

open Set

variable {G H : Type _} [LinearOrderedAddCommGroup G] [OrderedAddCommGroup H]

/-- An odd function on a linear ordered additive commutative group is strictly monotone on the whole
group provided that it is strictly monotone on `Set.Ici 0`. -/
theorem strictMono_of_odd_strictMonoOn_nonneg {f : G → H} (h₁ : ∀ x, f (-x) = -f x)
    (h₂ : StrictMonoOn f (Ici 0)) : StrictMono f := by
  refine' StrictMonoOn.Iic_union_Ici (fun x hx y hy hxy => neg_lt_neg_iff.1 _) h₂
  rw [← h₁, ← h₁]
  exact h₂ (neg_nonneg.2 hy) (neg_nonneg.2 hx) (neg_lt_neg hxy)
#align strict_mono_of_odd_strict_mono_on_nonneg strictMono_of_odd_strictMonoOn_nonneg

/-- An odd function on a linear ordered additive commutative group is strictly antitone on the whole
group provided that it is strictly antitone on `Set.Ici 0`. -/
theorem strictAnti_of_odd_strictAntiOn_nonneg {f : G → H} (h₁ : ∀ x, f (-x) = -f x)
    (h₂ : StrictAntiOn f (Ici 0)) : StrictAnti f :=
  @strictMono_of_odd_strictMonoOn_nonneg G Hᵒᵈ _ _ _ h₁ h₂
#align strict_anti_of_odd_strict_anti_on_nonneg strictAnti_of_odd_strictAntiOn_nonneg

/-- An odd function on a linear ordered additive commutative group is monotone on the whole group
provided that it is monotone on `Set.Ici 0`. -/
theorem monotone_of_odd_of_monotoneOn_nonneg {f : G → H} (h₁ : ∀ x, f (-x) = -f x)
    (h₂ : MonotoneOn f (Ici 0)) : Monotone f := by
  refine' MonotoneOn.Iic_union_Ici (fun x hx y hy hxy => neg_le_neg_iff.1 _) h₂
  rw [← h₁, ← h₁]
  exact h₂ (neg_nonneg.2 hy) (neg_nonneg.2 hx) (neg_le_neg hxy)
#align monotone_of_odd_of_monotone_on_nonneg monotone_of_odd_of_monotoneOn_nonneg

/-- An odd function on a linear ordered additive commutative group is antitone on the whole group
provided that it is monotone on `Set.Ici 0`. -/
theorem antitone_of_odd_of_monotoneOn_nonneg {f : G → H} (h₁ : ∀ x, f (-x) = -f x)
    (h₂ : AntitoneOn f (Ici 0)) : Antitone f :=
  @monotone_of_odd_of_monotoneOn_nonneg G Hᵒᵈ _ _ _ h₁ h₂
#align antitone_of_odd_of_monotone_on_nonneg antitone_of_odd_of_monotoneOn_nonneg


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

import section14polynomials.sheet03aux_ideal

/-!

This sheet contains a couple of trickier proofs about `aux_ideal`.

Here is an explanation of the first -- igt's called `coeff_span_lift_gens`.

Say R is Noetherian, and `c ∈ aux_ideal I n`. Recall that `hR.gens (aux_ideal I n)`
is a (finite) set which spans `aux_ideal I n` as an `R`-ideal. So in particular
we know that `c` is in the span of these generators.

The claim is that if we let `T : finset R[X]` be random `lift`s of these generators to `R[X]`,
then there's a polynomial `y` in the `R`-span of these lifts such that the coefficient of `X^n`
in `y` is `c` again.

One way of doing this would be to write `c` as a finite `R`-linear combination of the generators,
and then use the corresponding finite `R`-linear combination of the lifts. But that is a pain
to code.

The proof I came up with of this uses `submodule.span_image`, which says that the span
of the image equals the image of the span. In particular, the image of the lifts is just
the generators `hR.gens (aux_ideal I n)` again, so the image of the span is `aux_ideal I n`.
The theorem says that this means that the image of the `R`-span of the lifts in `R[X]` under
the `coeff` map is `aux_ideal I n` and so our element `y` comes for free.

A key intermediate step in my proof is

have h : (hR.gens (aux_ideal I n) : set R) ⊆ (polynomial.lcoeff R n)''
    (finset.image (λ (r : R), aux_ideal.lift I n r) (hR.gens (aux_ideal I n))),

saying that the generators are a subset of the image of the lift of the generators.
I don't need to prove equality. After that it's just `submodule.span_image` and unravelling.
More precisely, `hcI` tells us that `c` is in the span of the generators,
and `ideal.span_mono` reduces us to the case that `c` is in the span
of the image under an `R`-module map of some set (namely the image of `lift`),
By `submodule.span_image` we're now done.
-/

variables {R : Type} [comm_ring R] (I : ideal (polynomial R))
variables {n : ℕ} {r : R} [decidable (r ∈ aux_ideal I n)]

open polynomial

namespace aux_ideal

lemma coeff_span_lift_gens [∀ r, decidable (r ∈ aux_ideal I n)] [decidable_eq (polynomial R)]
  (hR : is_noetherian_ring R) {c : R} (hcI : c ∈ aux_ideal I n) :
∃ (y : polynomial R), y ∈ submodule.span R ((λ (r : R), lift I n r) '' ↑(hR.gens (aux_ideal I n))) ∧
  (lcoeff R n) y = c :=
begin
  sorry
end

/-

The conclusion of the previous lemma says that there's some polynomial
in `submodule.span R ((λ (r : R), lift I n r) '' ↑(hR.gens (aux_ideal I n)))`.
The below lemma shows that such a polynomial has degree at most `n`, and
the reason is simply that all the generators have degree at most `n`.
To solve this one you'll need to know `submodule.span_le` from mathlib
and `lift_nat_degree_le` from a previous sheet.
-/

lemma nat_deg_le_of_mem_span_lift_gens [∀ r, decidable (r ∈ aux_ideal I n)]
  (hR : is_noetherian_ring R) {p : polynomial R}
  (hp : p ∈ submodule.span R ((λ (r : R), lift I n r) '' (hR.gens (aux_ideal I n)))) :
p.nat_degree ≤ n :=
begin
  sorry,
end

end aux_ideal


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Kenny Lau
-/

import algebra.geom_sum
import ring_theory.ideal.basic

/-!
# Basic results in number theory

This file should contain basic results in number theory. So far, it only contains the essential
lemma in the construction of the ring of Witt vectors.

## Main statement

`dvd_sub_pow_of_dvd_sub` proves that for elements `a` and `b` in a commutative ring `R` and for
all natural numbers `p` and `k` if `p` divides `a-b` in `R`, then `p ^ (k + 1)` divides
`a ^ (p ^ k) - b ^ (p ^ k)`.
-/

section

open ideal ideal.quotient

lemma dvd_sub_pow_of_dvd_sub {R : Type*} [comm_ring R] {p : ℕ}
  {a b : R} (h : (p : R) ∣ a - b) (k : ℕ) :
  (p^(k+1) : R) ∣ a^(p^k) - b^(p^k) :=
begin
  induction k with k ih,
  { rwa [pow_one, pow_zero, pow_one, pow_one] },
  rw [pow_succ' p k, pow_mul, pow_mul, ← geom_sum₂_mul, pow_succ],
  refine mul_dvd_mul _ ih,
  let I : ideal R := span {p},
  let f : R →+* ideal.quotient I := mk I,
  have hp : (p : ideal.quotient I) = 0,
  { rw [← f.map_nat_cast, eq_zero_iff_mem, mem_span_singleton] },
  rw [← mem_span_singleton, ← ideal.quotient.eq] at h,
  rw [← mem_span_singleton, ← eq_zero_iff_mem, ring_hom.map_geom_sum₂,
      ring_hom.map_pow, ring_hom.map_pow, h, geom_sum₂_self, hp, zero_mul],
end

end


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

! This file was ported from Lean 3 source module ring_theory.localization.cardinality
! leanprover-community/mathlib commit 3b09a2601bb7690643936643e99bba0fedfbf6ed
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.SetTheory.Cardinal.Ordinal
import Mathbin.RingTheory.Artinian

/-!
# Cardinality of localizations

In this file, we establish the cardinality of localizations. In most cases, a localization has
cardinality equal to the base ring. If there are zero-divisors, however, this is no longer true -
for example, `zmod 6` localized at `{2, 4}` is equal to `zmod 3`, and if you have zero in your
submonoid, then your localization is trivial (see `is_localization.unique_of_zero_mem`).

## Main statements

* `is_localization.card_le`: A localization has cardinality no larger than the base ring.
* `is_localization.card`: If you don't localize at zero-divisors, the localization of a ring has
  cardinality equal to its base ring,

-/

open Cardinal nonZeroDivisors

universe u v

namespace IsLocalization

variable {R : Type u} [CommRing R] (S : Submonoid R) {L : Type u} [CommRing L] [Algebra R L]
  [IsLocalization S L]

include S

/-- A localization always has cardinality less than or equal to the base ring. -/
theorem card_le : (#L) ≤ (#R) := by
  classical
    cases fintypeOrInfinite R
    · exact Cardinal.mk_le_of_surjective (IsArtinianRing.localization_surjective S _)
    erw [← Cardinal.mul_eq_self <| Cardinal.aleph0_le_mk R]
    set f : R × R → L := fun aa => IsLocalization.mk' _ aa.1 (if h : aa.2 ∈ S then ⟨aa.2, h⟩ else 1)
    refine' @Cardinal.mk_le_of_surjective _ _ f fun a => _
    obtain ⟨x, y, h⟩ := IsLocalization.mk'_surjective S a
    use (x, y)
    dsimp [f]
    rwa [dif_pos <| show ↑y ∈ S from y.2, SetLike.eta]
#align is_localization.card_le IsLocalization.card_le

variable (L)

/-- If you do not localize at any zero-divisors, localization preserves cardinality. -/
theorem card (hS : S ≤ R⁰) : (#R) = (#L) :=
  (Cardinal.mk_le_of_injective (IsLocalization.injective L hS)).antisymm (card_le S)
#align is_localization.card IsLocalization.card

end IsLocalization


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Michael Howes
-/
import group_theory.quotient_group
import tactic.group

/-!
# The abelianization of a group

This file defines the commutator and the abelianization of a group. It furthermore prepares for the
result that the abelianization is left adjoint to the forgetful functor from abelian groups to
groups, which can be found in `algebra/category/Group/adjunctions`.

## Main definitions

* `commutator`: defines the commutator of a group `G` as a subgroup of `G`.
* `abelianization`: defines the abelianization of a group `G` as the quotient of a group by its
  commutator subgroup.
* `abelianization.map`: lifts a group homomorphism to a homomorphism between the abelianizations
* `mul_equiv.abelianization_congr`: Equivalent groups have equivalent abelianizations

-/

universes u v w

-- Let G be a group.
variables (G : Type u) [group G]

/-- The commutator subgroup of a group G is the normal subgroup
  generated by the commutators [p,q]=`p*q*p⁻¹*q⁻¹`. -/
@[derive subgroup.normal]
def commutator : subgroup G :=
subgroup.normal_closure {x | ∃ p q, p * q * p⁻¹ * q⁻¹ = x}

/-- The abelianization of G is the quotient of G by its commutator subgroup. -/
def abelianization : Type u :=
G ⧸ (commutator G)

namespace abelianization

local attribute [instance] quotient_group.left_rel

instance : comm_group (abelianization G) :=
{ mul_comm := λ x y, quotient.induction_on₂' x y $ λ a b,
  begin
    apply quotient.sound,
    apply subgroup.subset_normal_closure,
    use b⁻¹, use a⁻¹,
    group,
  end,
.. quotient_group.quotient.group _ }

instance : inhabited (abelianization G) := ⟨1⟩

instance [fintype G] [decidable_pred (∈ commutator G)] :
  fintype (abelianization G) :=
quotient_group.fintype (commutator G)

variable {G}

/-- `of` is the canonical projection from G to its abelianization. -/
def of : G →* abelianization G :=
{ to_fun := quotient_group.mk,
  map_one' := rfl,
  map_mul' := λ x y, rfl }

@[simp] lemma mk_eq_of (a : G) : quot.mk _ a = of a := rfl

section lift
-- So far we have built Gᵃᵇ and proved it's an abelian group.
-- Furthremore we defined the canonical projection `of : G → Gᵃᵇ`

-- Let `A` be an abelian group and let `f` be a group homomorphism from `G` to `A`.
variables {A : Type v} [comm_group A] (f : G →* A)

lemma commutator_subset_ker : commutator G ≤ f.ker :=
begin
  apply subgroup.normal_closure_le_normal,
  rintros x ⟨p, q, rfl⟩,
  simp [monoid_hom.mem_ker, mul_right_comm (f p) (f q)],
end

/-- If `f : G → A` is a group homomorphism to an abelian group, then `lift f` is the unique map from
  the abelianization of a `G` to `A` that factors through `f`. -/
def lift : (G →* A) ≃ (abelianization G →* A) :=
{ to_fun := λ f, quotient_group.lift _ f (λ x h, f.mem_ker.2 $ commutator_subset_ker _ h),
  inv_fun := λ F, F.comp of,
  left_inv := λ f, monoid_hom.ext $ λ x, rfl,
  right_inv := λ F, monoid_hom.ext $ λ x, quotient_group.induction_on x $ λ z, rfl }

@[simp] lemma lift.of (x : G) : lift f (of x) = f x :=
rfl

theorem lift.unique
  (φ : abelianization G →* A)
  -- hφ : φ agrees with f on the image of G in Gᵃᵇ
  (hφ : ∀ (x : G), φ (of x) = f x)
  {x : abelianization G} :
  φ x = lift f x :=
quotient_group.induction_on x hφ

@[simp] lemma lift_of : lift of = monoid_hom.id (abelianization G) :=
lift.apply_symm_apply $ monoid_hom.id _

end lift

variables {A : Type v} [monoid A]

/-- See note [partially-applied ext lemmas]. -/
@[ext]
theorem hom_ext (φ ψ : abelianization G →* A)
  (h : φ.comp of = ψ.comp of) : φ = ψ :=
monoid_hom.ext $ λ x, quotient_group.induction_on x $ monoid_hom.congr_fun h

section map

variables {H : Type v} [group H] (f : G →* H)

/-- The map operation of the `abelianization` functor -/
def map : abelianization G →* abelianization H := lift (of.comp f)

@[simp]
lemma map_of (x : G) : map f (of x) = of (f x) := rfl

@[simp]
lemma map_id : map (monoid_hom.id G) = monoid_hom.id (abelianization G) := hom_ext _ _ rfl

@[simp]
lemma map_comp {I : Type w} [group I] (g : H →* I) :
  (map g).comp (map f) = map (g.comp f) := hom_ext _ _ rfl

@[simp]
lemma map_map_apply {I : Type w} [group I] {g : H →* I} {x : abelianization G}:
  map g (map f x) = map (g.comp f) x := monoid_hom.congr_fun (map_comp _ _) x

end map

end abelianization

section abelianization_congr

variables {G} {H : Type v} [group H] (e : G ≃* H)

/-- Equivalent groups have equivalent abelianizations -/
def mul_equiv.abelianization_congr : abelianization G ≃* abelianization H :=
{ to_fun := abelianization.map e.to_monoid_hom,
  inv_fun := abelianization.map e.symm.to_monoid_hom,
  left_inv := by { rintros ⟨a⟩, simp },
  right_inv := by { rintros ⟨a⟩, simp },
  map_mul' := monoid_hom.map_mul _ }

@[simp]
lemma abelianization_congr_of (x : G) :
  (e.abelianization_congr) (abelianization.of x) = abelianization.of (e x) := rfl

@[simp]
lemma abelianization_congr_refl :
  (mul_equiv.refl G).abelianization_congr = mul_equiv.refl (abelianization G) :=
mul_equiv.to_monoid_hom_injective abelianization.lift_of

@[simp]
lemma abelianization_congr_symm  :
  e.abelianization_congr.symm = e.symm.abelianization_congr := rfl

@[simp]
lemma abelianization_congr_trans {I : Type v} [group I] (e₂ : H ≃* I) :
  e.abelianization_congr.trans e₂.abelianization_congr = (e.trans e₂).abelianization_congr :=
mul_equiv.to_monoid_hom_injective (abelianization.hom_ext _ _ rfl)

end abelianization_congr

/-- An Abelian group is equivalent to its own abelianization. -/
@[simps] def abelianization.equiv_of_comm {H : Type*} [comm_group H] :
  H ≃* abelianization H :=
{ to_fun    := abelianization.of,
  inv_fun   := abelianization.lift (monoid_hom.id H),
  left_inv  := λ a, rfl,
  right_inv := by { rintros ⟨a⟩, refl, },
  .. abelianization.of }


import Mynat.Add

namespace mynat

-- Not sure if I'm correct by writing these lines...
-- Seems like I cannot copy directly from https://github.com/ImperialCollegeLondon/natural_number_game/blob/master/src/mynat/definition.lean
axiom zero_ne_succ (m : mynat) : (0 : mynat) ≠ succ m
axiom succ_inj {m n : mynat} (h : succ m = succ n) : m = n

theorem succ_inj' {a b : mynat} (hs : (succ a) = (succ b)) : a = b := by
  apply succ_inj
  exact hs

theorem succ_succ_inj {a b : mynat} (h : succ (succ a) = succ (succ b)) : a = b := by
  apply succ_inj
  apply succ_inj
  exact h

theorem succ_eq_succ_of_eq {a b : mynat} : a = b → (succ a) = (succ b) := by
  intro h
  rw [h]

theorem succ_eq_succ_iff (a b : mynat) : succ a = succ b ↔ a = b := by
  apply Iff.intro
  . exact succ_inj'
  . exact succ_eq_succ_of_eq

theorem add_right_cancel (a t b : mynat) : a + t = b + t → a = b := by
  cases t
  case zero =>
    rw [mynat_zero_eq_zero]
    rw [add_zero]
    rw [add_zero]
    intro H
    exact H
  case succ t' =>
    rw [add_succ]
    rw [add_succ]
    intro h
    have hh := succ_inj h
    exact (add_right_cancel a t' b) hh

theorem add_left_cancel (t a b : mynat) : t + a = t + b → a = b := by
  rw [add_comm t a]
  rw [add_comm t b]
  exact add_right_cancel a t b

theorem add_right_cancel_iff (t a b : mynat) :  a + t = b + t ↔ a = b := by
  apply Iff.intro
  . exact add_right_cancel a t b
  . intro H
    rw [H]

theorem eq_zero_of_add_right_eq_self {a b : mynat} : a + b = a → b = 0 := by
  intro h
  conv at h =>
    rhs
    rw [← add_zero a]
  have hcanc := add_left_cancel a b 0
  exact hcanc h

theorem succ_ne_zero (a : mynat) : succ a ≠ 0 := by
  intro h
  have hfalse := zero_ne_succ a
  exact hfalse (Eq.symm h)

theorem add_left_eq_zero {{a b : mynat}} (H : a + b = 0) : b = 0 := by
  cases b
  case zero =>
    rfl
  case succ b' =>
    rw [add_succ] at H
    have h' := (succ_ne_zero (a+b')) H
    apply False.elim
    exact h'

theorem add_right_eq_zero {a b : mynat} : a + b = 0 → a = 0 := by
  rw [add_comm]
  intro h
  exact add_left_eq_zero h

theorem add_one_eq_succ (d : mynat) : d + 1 = succ d := by
  rw [succ_eq_add_one]

theorem ne_succ_self (n : mynat) : n ≠ succ n := by
  intro H
  rw [← add_one_eq_succ n] at H
  conv at H =>
    lhs
    rw [← add_zero n]
  have hfalse := add_left_cancel n 0 1 H
  rw [one_eq_succ_zero] at hfalse
  exact zero_ne_succ 0 hfalse

end mynat


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import sesion_4.soluciones.limites

import topology.instances.real

/-
# Límites de secuencias en Lean

Ahora que sabemos cómo utilizar filtros en Lean, podemos reescribir
las soluciones de los ejercicios de límites de la sesión 4.

-/

open filter

open_locale topology -- para acceder a la notación 𝓝

namespace limites

-- Nuestra definición `tends_to` es equivalente a `filter.tendsto`
lemma tends_to_iff_tendsto (a : ℕ → ℝ) (l : ℝ) :
  tends_to a l ↔ tendsto a at_top (𝓝 l) :=
begin
  rw metric.tendsto_at_top,
  rw tends_to,
  congr',
end

/-- El límite de la secuencia constante con valor `c` es `c`. -/
theorem tends_to_const (c : ℝ) : tends_to (λ n, c) c :=
begin
  rw tends_to_iff_tendsto,
  sorry,
end

/-- Si `a(n)` tiende a `L` entonces `a(n) + c` tiende a `t + c` -/
theorem tends_to_add_const {a : ℕ → ℝ} {L : ℝ} (c : ℝ)
  (h : tends_to a L) :
  tends_to (λ n, a n + c) (L + c) :=
begin
  rw tends_to_iff_tendsto at *,
  sorry,
end

/-- Si `a(n)` tiende a `L`, entonces `-a(n)` tiende a `-L`.
Si simplificar la expresión dentro del valor absoluto te está
dando problemas, ve a la hoja `reales.lean`.
-/
theorem tends_to_neg {a : ℕ → ℝ} {L : ℝ} (ha : tends_to a L) :
  tends_to (λ n, - a n) (-L) :=
begin
  sorry,
end

/-- Si `a(n)` tiende a `La` y `b(n)` tiende a `Lb` entonces `a(n) + b(n)`
  tiende a `La + Lb`. -/
theorem tends_to_add {a b : ℕ → ℝ} {La Lb : ℝ}
  (ha : tends_to a La) (hb : tends_to b Lb) :
  tends_to (λ n, a n + b n) (La + Lb) :=
begin
  sorry,
end

/-- Si `a(n)` tiende a `La` y `b(n)` tiende a `Lb` entonces `a(n) - b(n)`
tiende a `La - Lb`. -/
theorem tends_to_sub {a b : ℕ → ℝ} {La Lb : ℝ}
  (ha : tends_to a La) (hb : tends_to b Lb) :
  tends_to (λ n, a n - b n) (La - Lb) :=
begin
  sorry,
end

/-- Si `a(n)` tiende a `L`, entonces `c*a(n)` tiende a `c*L`.
-/
lemma tends_to_mul_const_left {a : ℕ → ℝ} {L c : ℝ} (h : tends_to a L) :
  tends_to (λ n, c * (a n)) (c * L) :=
begin
  sorry
end

/- Lema del sandwich.
  Probad a buscar `sandwich` o `squeeze` en la documentación de mathlib.
-/
theorem sandwich (a b c : ℕ → ℝ) (L : ℝ) (ha : tends_to a L) (hc : tends_to c L)
  (hab : ∀ n, a n ≤ b n) (hbc : ∀ n, b n ≤ c n) :
  tends_to b L :=
begin
  sorry
end

/-- Si `a(n)` tiende a `La` y `b(n)` tiende a `Lb` entonces `a(n) * b(n)`
  tiende a `La * Lb`. -/
theorem tends_to_mul {a b : ℕ → ℝ} {La Lb : ℝ}
  (ha : tends_to a La) (hb : tends_to b Lb) :
  tends_to (λ n, a n * b n) (La * Lb) :=
begin
  sorry
end

end limites


import tactic
import .«2_1_part_ii_natural_numbers»

namespace notes

-- Add this line in the beginning of each file to override the default ℕ
local notation `ℕ` : 1024 := mynat

universe u

--------------------------------------------------------------------------------
-- # Integers

namespace myint

-- The equivalence relation
  def eqv : ℕ × ℕ → ℕ × ℕ → Prop :=
    λ ⟨a, b⟩ ⟨c, d⟩, a + d = c + b

  local infix `∼` : 50 := eqv

  lemma eqv_refl : ∀ x, x ∼ x :=
    λ ⟨a, b⟩, rfl

  lemma eqv_symm : ∀ x y, x ∼ y → y ∼ x :=
    λ ⟨a, b⟩ ⟨c, d⟩ h, h.symm

  lemma eqv_trans : ∀ x y z, x ∼ y → y ∼ z → x ∼ z :=
    λ ⟨a, b⟩ ⟨c, d⟩ ⟨e, f⟩ h₁ h₂, by
    { unfold eqv at *,
      have h : (d + c) + (a + f) = (d + c) + (e + b),
      { -- `rewrite_search` is my dream tactic!
        /- rewrite_search? [h₁, h₂] -/
        conv_lhs { congr, skip, erw [mynat.add_comm] },
        conv_lhs { erw [mynat.add_assoc, mynat.add_comm] },
        conv_lhs { congr, erw [←mynat.add_assoc] },
        conv_lhs { erw [mynat.add_assoc] },
        conv_lhs { congr, erw [h₂] },
        conv_rhs { erw [←mynat.add_assoc] },
        conv_rhs { congr, erw [mynat.add_comm, ←mynat.add_assoc] },
        conv_rhs { erw [mynat.add_assoc] },
        conv_rhs { congr, skip, erw [←h₁] }
        /- end -/ },
      exact mynat.add_left_cancel _ _ _ h }

  lemma eqv_equivalence_relation : is_equivalence_relation (∼) :=
    ⟨eqv_refl, eqv_symm, eqv_trans⟩

end myint

-- Definition of ℤ as a quotient type
-- (i.e. equivalence classes under the equivalence relation)
def myint : Type :=
  @quot (ℕ × ℕ) myint.eqv

-- Add this line in the beginning of each file to override the default ℤ
local notation `ℤ` : 1024 := myint

namespace myint
section
  local infix ` ∼ ` : 50 := eqv

-- Addition
  def add_fn : ℕ × ℕ → ℕ × ℕ → ℕ × ℕ :=
    λ ⟨a, b⟩ ⟨c, d⟩, (a + c, b + d)

  lemma add_respects_fst :
    ∀ (x₁ x₂ y : ℕ × ℕ), (x₁ ∼ x₂) → (add_fn x₁ y ∼ add_fn x₂ y)
  :=
  begin
    rintros ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ ⟨c, d⟩ h,
    unfold add_fn at *,
    unfold eqv at *,
    /- rewrite_search? [h] -/
    conv_lhs { congr, erw [mynat.add_comm] },
    conv_lhs { erw [mynat.add_assoc] },
    conv_lhs { congr, skip, erw [←mynat.add_assoc] },
    conv_lhs { congr, skip, congr, erw [h] },
    conv_rhs { congr, erw [mynat.add_comm] },
    conv_rhs { erw [mynat.add_assoc] },
    conv_rhs { congr, skip, erw [←mynat.add_assoc] }
    /- end -/
  end

  lemma add_respects_snd :
    ∀ (x y₁ y₂ : ℕ × ℕ), (y₁ ∼ y₂) → (add_fn x y₁ ∼ add_fn x y₂)
  :=
  begin
    rintros ⟨a, b⟩ ⟨c₁, d₁⟩ ⟨c₂, d₂⟩ h,
    unfold add_fn at *,
    unfold eqv at *,
    /- rewrite_search? [h] -/
    conv_lhs { congr, skip, erw [mynat.add_comm] },
    conv_lhs { erw [mynat.add_assoc] },
    conv_lhs { congr, skip, erw [←mynat.add_assoc] },
    conv_lhs { congr, skip, congr, erw [h] },
    conv_rhs { congr, skip, erw [mynat.add_comm] },
    conv_rhs { erw [mynat.add_assoc] },
    conv_rhs { congr, skip, erw [←mynat.add_assoc] }
    /- end -/
  end

  def add : ℤ → ℤ → ℤ :=
    quot.map₂ add_fn add_respects_snd add_respects_fst
  instance : has_add ℤ := ⟨add⟩

-- Negation
  def neg_fn : ℕ × ℕ → ℕ × ℕ :=
    λ ⟨a, b⟩, (b, a)

  lemma neg_respects :
    ∀ (x₁ x₂ : ℕ × ℕ), (x₁ ∼ x₂) → (neg_fn x₁ ∼ neg_fn x₂)
  :=
  begin
    rintros ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ h,
    unfold neg_fn at *,
    unfold eqv at *,
    rw [mynat.add_comm, ← h, mynat.add_comm]
  end

  def neg : ℤ → ℤ :=
    quot.map neg_fn neg_respects
  instance : has_neg ℤ := ⟨neg⟩

-- Multiplication
  def mul_fn : ℕ × ℕ → ℕ × ℕ → ℕ × ℕ :=
    λ ⟨a, b⟩ ⟨c, d⟩, (a * c + b * d, a * d + b * c)

  lemma mul_respects_fst :
    ∀ (x₁ x₂ y : ℕ × ℕ), (x₁ ∼ x₂) → (mul_fn x₁ y ∼ mul_fn x₂ y)
  :=
  begin
    rintros ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ ⟨c, d⟩ h,
    unfold mul_fn at *,
    unfold eqv at *,
    have : (a₁ + b₂) * c + (a₂ + b₁) * d = (a₂ + b₁) * c + (a₁ + b₂) * d,
    { rw h },
    calc  a₁ * c + b₁ * d + (a₂ * d + b₂ * c)
        = (a₁ + b₂) * c + (a₂ + b₁) * d : by
        { /- rewrite_search? -/
          conv_lhs { congr, skip, erw [mynat.add_comm] },
          conv_lhs { erw [mynat.add_assoc] },
          conv_lhs { congr, skip, erw [mynat.add_comm, mynat.add_assoc] },
          conv_rhs { congr, erw [mynat.add_mul], skip, erw [mynat.add_mul] },
          conv_rhs { erw [mynat.add_assoc] }
          /- end -/ }
    ... = (a₂ + b₁) * c + (a₁ + b₂) * d : by rw h
    ... = a₂ * c + b₂ * d + (a₁ * d + b₁ * c) : by
        { /- rewrite_search? -/
          conv_lhs { congr, erw [mynat.add_mul], skip, erw [mynat.add_mul] },
          conv_lhs { erw [mynat.add_assoc] },
          conv_rhs { erw [mynat.add_assoc] },
          conv_rhs { congr, skip, erw [←mynat.add_assoc, mynat.add_comm] },
          conv_rhs { congr, skip, congr, skip, erw [mynat.add_comm] }
          /- end -/ }
  end

  lemma mul_respects_snd :
    ∀ (x y₁ y₂ : ℕ × ℕ), (y₁ ∼ y₂) → (mul_fn x y₁ ∼ mul_fn x y₂)
  :=
  begin
    rintros ⟨a, b⟩ ⟨c₁, d₁⟩ ⟨c₂, d₂⟩ h,
    have : mul_fn ⟨a, b⟩ ⟨c₁, d₁⟩ = mul_fn ⟨c₁, d₁⟩ ⟨a, b⟩,
    { unfold mul_fn at *, cc },
    rw this,
    have : mul_fn ⟨a, b⟩ ⟨c₂, d₂⟩ = mul_fn ⟨c₂, d₂⟩ ⟨a, b⟩,
    { unfold mul_fn at *, cc },
    rw this,
    exact mul_respects_fst _ _ _ h,
  end

  def mul : ℤ → ℤ → ℤ :=
    quot.map₂ mul_fn mul_respects_snd mul_respects_fst
  instance : has_mul ℤ := ⟨mul⟩

-- Less or equal than
  def le_fn : ℕ × ℕ → ℕ × ℕ → Prop :=
    λ ⟨a, b⟩ ⟨c, d⟩, a + d ≤ c + b

  lemma le_respects_fst :
    ∀ (x₁ x₂ y : ℕ × ℕ), x₁ ∼ x₂ → le_fn x₁ y = le_fn x₂ y
  :=
  begin
    rintros ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ ⟨c, d⟩ h,
    unfold le_fn at *,
    unfold eqv at *,
    apply propext,
    split,
    { intros h₁, sorry },
    { intros h₁, sorry }
  end

  lemma le_respects_snd :
    ∀ (x y₁ y₂ : ℕ × ℕ), (y₁ ∼ y₂) → le_fn x y₁ = le_fn x y₂
  :=
  begin
    rintros ⟨a, b⟩ ⟨c₁, d₁⟩ ⟨c₂, d₂⟩ h,
    unfold le_fn at *,
    unfold eqv at *,
    sorry
  end

  def le : ℤ → ℤ → Prop :=
    quot.lift₂ le_fn le_respects_snd le_respects_fst
  instance : has_le ℤ := ⟨le⟩

-- Less than
  def lt_fn : ℕ × ℕ → ℕ × ℕ → Prop :=
    λ ⟨a, b⟩ ⟨c, d⟩, a + d < c + b

  lemma lt_respects_fst :
    ∀ (x₁ x₂ y : ℕ × ℕ), x₁ ∼ x₂ → lt_fn x₁ y = lt_fn x₂ y
  :=
  begin
    rintros ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ ⟨c, d⟩ h,
    unfold lt_fn at *,
    unfold eqv at *,
    sorry
  end

  lemma lt_respects_snd :
    ∀ (x y₁ y₂ : ℕ × ℕ), (y₁ ∼ y₂) → lt_fn x y₁ = lt_fn x y₂
  :=
  begin
    rintros ⟨a, b⟩ ⟨c₁, d₁⟩ ⟨c₂, d₂⟩ h,
    unfold lt_fn at *,
    unfold eqv at *,
    sorry
  end

  def lt : ℤ → ℤ → Prop :=
    quot.lift₂ lt_fn lt_respects_snd lt_respects_fst
  instance : has_lt ℤ := ⟨lt⟩

end

section
-- TODO: ℕ as a subset of ℤ

  def zero : ℤ := quot.mk eqv (0, 0)
  def one : ℤ := quot.mk eqv (1, 0)

  instance : has_zero ℤ := ⟨zero⟩
  instance : has_one ℤ := ⟨one⟩

  lemma zero_ne_one :
    (0 : ℤ) ≠ 1
  :=
  begin
    intros h,
    let f : ℕ × ℕ → Prop := λ ⟨a, b⟩, ite (b = 0) false true,
    sorry
  end
end

section
  local infix ` ∼ ` : 50 := eqv
  variables (x y z : ℤ)

  @[simp, rewrite]
  theorem add_assoc :
    (x + y) + z = x + (y + z)
  :=
  begin
    revert x y z, rintros ⟨a, b⟩ ⟨c, d⟩ ⟨e, f⟩,
    change (quot.mk eqv (a + c + e, b + d + f) =
            quot.mk eqv (a + (c + e), b + (d + f))),
    rw [add_assoc, add_assoc]
  end

  @[simp, rewrite]
  theorem zero_add :
    0 + x = x
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (0 + a, 0 + b) = quot.mk eqv (a, b)),
    rw [zero_add, zero_add]
  end

  @[simp, rewrite]
  theorem add_zero :
    x + 0 = x
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (a + 0, b + 0) = quot.mk eqv (a, b)),
    refl
  end

  @[simp, rewrite]
  theorem neg_add :
    (-x) + x = 0
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (b + a, a + b) = quot.mk eqv (0, 0)),
    rw add_comm,
    apply quot.sound,
    unfold eqv,
    simp
  end

  @[simp, rewrite]
  theorem add_neg :
    x + (-x) = 0
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (a + b, b + a) = quot.mk eqv (0, 0)),
    rw add_comm,
    apply quot.sound,
    unfold eqv,
    simp
  end

  @[rewrite]
  theorem add_comm :
    x + y = y + x
  :=
  begin
    revert x y, rintros ⟨a, b⟩ ⟨c, d⟩,
    change (quot.mk eqv (a + c, b + d) = quot.mk eqv (c + a, d + b)),
    rw [add_comm a c, add_comm b d]
  end

  @[simp, rewrite]
  theorem mul_assoc :
    (x * y) * z = x * (y * z)
  :=
  begin
    revert x y z, rintros ⟨a, b⟩ ⟨c, d⟩ ⟨e, f⟩,
    change (quot.mk eqv ((a * c + b * d) * e + (a * d + b * c) * f, (a * c + b * d) * f + (a * d + b * c) * e) =
            quot.mk eqv (a * (c * e + d * f) + b * (c * f + d * e), a * (c * f + d * e) + b * (c * e + d * f))),
    simp only [mynat.mul_assoc, mynat.add_assoc, mynat.mul_add, mynat.add_mul],
    apply quot.sound,
    unfold eqv,
    simp only [mynat.add_assoc, add_right_inj],
    /- TODO: how to automate here? `ac_refl` does not work... -/
    conv_lhs { rw mynat.add_comm }, simp only [mynat.add_assoc],
    conv_lhs { congr, skip, congr, skip, rw mynat.add_comm, congr, rw mynat.add_comm, congr, rw mynat.add_comm, congr, rw mynat.add_comm }, simp only [mynat.add_assoc],
    conv_lhs { congr, skip, congr, skip, congr, skip, rw mynat.add_comm, congr, rw mynat.add_comm, congr, rw mynat.add_comm }, simp only [mynat.add_assoc],
    conv_lhs { congr, skip, congr, skip, congr, skip, congr, skip, rw mynat.add_comm, congr, rw mynat.add_comm }, simp only [mynat.add_assoc],
    conv_lhs { congr, skip, congr, skip, congr, skip, congr, skip, congr, skip, rw mynat.add_comm },
    /- rhs: `a * (d * f) + (b * (c * f) + (b * (d * e) + (a * (c * f) + (b * (d * f) + (a * (d * e) + b * (c * e))))))` -/
  end

  @[simp, rewrite]
  theorem one_mul :
    1 * x = x
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (1 * a + 0 * b, 1 * b + 0 * a) = quot.mk eqv (a, b)),
    simp
  end

  @[simp, rewrite]
  theorem mul_one :
    x * 1 = x
  :=
  begin
    revert x, rintros ⟨a, b⟩,
    change (quot.mk eqv (a * 1 + b * 0, a * 0 + b * 1) = quot.mk eqv (a, b)),
    simp
  end

  @[rewrite]
  theorem mul_comm :
    x * y = y * x
  :=
  begin
    revert x y, rintros ⟨a, b⟩ ⟨c, d⟩,
    change (quot.mk eqv (a * c + b * d, a * d + b * c) =
            quot.mk eqv (c * a + d * b, c * b + d * a)),
    rw [mul_comm a c, mul_comm b d, mul_comm a d, mul_comm b c, mynat.add_comm (d * a)]
  end

  @[simp, rewrite]
  theorem add_mul :
    (x + y) * z = x * z + y * z
  :=
  begin
    revert x y z, rintros ⟨a, b⟩ ⟨c, d⟩ ⟨e, f⟩,
    change (quot.mk eqv ((a + c) * e + (b + d) * f, (a + c) * f + (b + d) * e) =
            quot.mk eqv ((a * e + b * f) + (c * e + d * f), (a * f + b * e) + (c * f + d * e))),
    simp only [add_mul],
    ac_refl
  end

  @[simp, rewrite]
  lemma mul_add : x * (y + z) = x * y + x * z :=
    by rw [mul_comm, add_mul, mul_comm y x, mul_comm z x]

end

section
  local infix ` ∼ ` : 50 := eqv
  variables (x y z : ℤ)

  theorem add_right_cancel :
    x + z = y + z → x = y
  :=
  begin
    intros h,
    calc  x
        = x + z + -z : by rw [add_assoc, add_neg, add_zero]
    ... = y + z + -z : by rw h
    ... = y          : by rw [add_assoc, add_neg, add_zero]
  end

  lemma add_left_cancel : z + x = z + y → x = y :=
    by { rw [add_comm z x, add_comm z y], exact add_right_cancel _ _ _ }

  theorem mul_right_cancel :
    z ≠ 0 → x * z = y * z → x = y
  :=
  begin
    revert x y z, rintros ⟨a, b⟩ ⟨c, d⟩ ⟨e, f⟩ hef h,
    rcases mynat.lt_trichotomy e f with ⟨g, hg⟩ | ⟨g, hg₁, hg₂⟩ | ⟨g, hg₁, hg₂⟩,
    apply quot.sound,
    { sorry },
    { have h := calc
            quot.mk eqv (a * g, b * g)
          = quot.mk eqv (a * e + b * f, a * f + b * e) : by sorry
      ... = quot.mk eqv (c * e + d * f, c * f + d * e) : h
      ... = quot.mk eqv (c * g, d * g) : by sorry,
      -- change a * g + d * g = c * g + b * g at h,
      sorry },
    { sorry }
  end

  lemma mul_left_cancel : x ≠ 0 → x * y = x * z → y = z :=
    by { rw [mul_comm x y, mul_comm x z], exact mul_right_cancel _ _ _ }

end

-- TODO: lemmas related to le, lt...

end myint

--------------------------------------------------------------------------------

end notes


import tactic

/-!

# Groups

Definition and basic properties of a group.

-/

-- Technical note: We work in a namespace `xena` because Lean already has groups.
namespace xena
-- Now our definition of a group will really be called `xena.group`.

/-

## Definition of a group

The `group` class will extend `has_mul`, `has_one` and `has_inv`.

`has_mul G` means that `G` has a multiplication `* : G → G → G`
`has_one G` means that `G` has a `1 : G`
`has_inv G` means that `G` has an `⁻¹ : G → G`

All of `*`, `1` and `⁻¹` are notation for functions -- no axioms yet.

A `group` has all of this notation, and the group axioms too.
Let's now define the group class.
-/

/-- A `group` structure on a type `G` is multiplication, identity and inverse,
plus the usual axioms -/
class group (G : Type) extends has_mul G, has_one G, has_inv G :=
(mul_assoc : ∀ (a b c : G), a * b * c = a * (b * c))
(one_mul : ∀ (a : G), 1 * a = a)
(mul_left_inv : ∀ (a : G), a⁻¹ * a = 1)

/-

Formally, a term of type `group G` is now the following data:
a multiplication, 1, and inverse function,
and proofs that the group axioms are satisfied.

The way to say "let G be a group" is now `(G : Type) [group G]`

The square bracket notation is the notation used for classes.
Formally, it means "put a term of type `group G` into the type class
inference system". In practice this just means "you can use group
notation and axioms in proofs, and Lean will figure out why they're true"

We have been extremely mean with our axioms. Some authors also add
the axioms `mul_one : ∀ (a : G), a * 1 = a`
and `mul_right_inv : ∀ (a : G), a * a⁻¹ = 1`.

But these follow from the three axioms we used. Our first job is
to prove them. As you might imagine, mathematically this is pretty
much the trickiest part, because we have to be careful not to
accidentally assume these axioms when we're proving them.

Here are the four lemmas we will prove next.

`mul_left_cancel : ∀ (a b c : G), a * b = a * c → b = c`
`mul_eq_of_eq_inv_mul {a x y : G} : x = a⁻¹ * y → a * x = y`
`mul_one (a : G) : a * 1 = a`
`mul_right_inv (a : G) : a * a⁻¹ = 1`
-/

-- We're proving things about groups so let's work in the `group` namespace
-- (really this is `xena.group`)

namespace group

-- let `G` be a group.
variables {G : Type} [group G]

/-
We start by proving `mul_left_cancel : ∀ a b c, a * b = a * c → b = c`.
We assume `Habac : a * b = a * c` and deduce `b = c`. I've written
down the maths proof. Your job is to supply the rewrites that are
necessary to justify each step. Each rewrite is either one of
the axioms of a group, or an assumption. A reminder of the axioms:

`mul_assoc : ∀ (a b c : G), a * b * c = a * (b * c)`
`one_mul : ∀ (a : G), 1 * a = a`
`mul_left_inv : ∀ (a : G), a⁻¹ * a = 1`

This proof could be done using rewrites, but I will take this opportunity
to introduce the `calc` tactic.
-/
lemma mul_left_cancel (a b c : G) (Habac : a * b = a * c) : b = c :=
begin
 calc b = 1 * b         : by rw one_mul
    ... = (a⁻¹ * a) * b : by sorry -- replace `sorry` with `rw X` as appropriate
    ... = a⁻¹ * (a * b) : by sorry
    ... = a⁻¹ * (a * c) : by sorry
    ... = (a⁻¹ * a) * c : by sorry
    ... = 1 * c         : by sorry
    ... = c             : by sorry
end

/-
Next we prove that if `x = a⁻¹ * y` then `a * x = y`. Remember we are still
missing `mul_one` and `mul_right_inv`. A proof that avoids them is
the following: we want `a * x = y`. Now `apply`ing the previous lemma, it
suffices to prove that `a⁻¹ * (a * x) = a⁻¹ * y.`
Now use associativity and left cancellation on on the left, to reduce
to `h`.

Note that `mul_left_cancel` is a function, and its first input is
called `a`, but you had better give it `a⁻¹` instead.
-/
lemma mul_eq_of_eq_inv_mul {a x y : G} (h : x = a⁻¹ * y) : a * x = y :=
begin
  apply mul_left_cancel a⁻¹,
  sorry
end

-- It's a bore to keep introducing variable names.

-- Let `a,b,c,x,y` be elements of `G`.
variables (a b c x y : G)

/-
We can use `mul_eq_of_eq_inv_mul` to prove the two "missing" axioms `mul_one`
and `mul_right_inv`, and then our lives will be much easier. Try `apply`ing it
in the theorems below.
-/

@[simp] theorem mul_one : a * 1 = a :=
begin
  sorry
end

@[simp] theorem mul_right_inv : a * a⁻¹ = 1 :=
begin
  sorry
end

-- Now let's talk about what that `@[simp]` means.

/-

## Lean's simplifier

A human sees `a * a⁻¹` in group theory, and instantly replaces it with `1`.
We are going to train a simple AI called `simp` to do the same thing.

Lean's simplifier `simp` is a "term rewriting system". This means
that if you teach it a bunch of theorems of the form `A = B` or
`P ↔ Q` (by tagging them with the `@[simp]` attribute) and then give
it a complicated goal, like

`example : (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1`

then it will try to use the `rw` tactic as much as it can, using the lemmas
it has been taught, in an attempt to simplify the goal. If it manages
to solve it completely, then great! If it does not, but you feel like
it should have done, you might want to tag more lemmas with `@[simp]`.
`simp` should only be used to completely close goals. We are now
going to train the simplifier to solve the example above (indeed, we are
going to train it to reduce an arbitrary element of a free group into
a unique normal form, so it will solve any equalities which are true
for all groups, like the example above).

## Important note

Lean's simplifier does a series of rewrites, each one replacing something
with something else. But the simplifier will always rewrite from left to right!
If you tell it that `A = B` is a `simp` lemma then it will replace `A`s with
`B`s, but it will never replace `B`s with `A`s. If you tag a proof
of `A = B` with `@[simp]` and you also tag a proof of `B = A` with
`@[simp]`, then the simplifier will get stuck in an infinite loop when
it runs into an `A`! Equality should not be thought of as symmetric here.

Because the simplifier works from left to right, an important
rule of thumb is that if `A = B` is a `simp` lemma, then `B` should
probably be simpler than `A`! In particular, equality should not be
thought of as symmetric here. It is not a coincidence that in
the theorems below

`@[simp] theorem mul_one (a : G) : a * 1 = a`
`@[simp] theorem mul_right_inv (a : G) : a * a⁻¹ = 1`

the right hand side is simpler than the left hand side. It would be a
> disaster to tag `a = a * 1` with the `@[simp]` tag -- can you see why?

Let's train Lean's simplifier! Let's teach it the axioms of a `group` next.
We have already done the axioms, so we have to retrospectively tag
them with the `@[simp]` attribute.
-/

attribute [simp] one_mul mul_left_inv mul_assoc

/-
Now let's teach the simplifier the following five lemmas:

`inv_mul_cancel_left : a⁻¹ * (a * b) = b`
`mul_inv_cancel_left : a * (a⁻¹ * b) = b`
`inv_mul : (a * b)⁻¹ = b⁻¹ * a⁻¹`
`one_inv : (1 : G)⁻¹ = 1`
`inv_inv : (a⁻¹)⁻¹ = a`

Note that in each case, the right hand side is simpler
than the left hand side.

Try using the simplifier in your proofs! I will do the
first one for you.

-/

@[simp] lemma inv_mul_cancel_left : a⁻¹ * (a * b) = b :=
begin
  rw ← mul_assoc, -- the simplifier wouldn't do it that way
                  -- so we have to do it manually
  simp, -- simplifier takes it from here,
        -- rewriting a⁻¹ * a to 1 and then 1 * b to b
end

@[simp] lemma mul_inv_cancel_left : a * (a⁻¹ * b) = b :=
begin
  sorry
end

@[simp] lemma inv_mul : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  sorry
end

@[simp] lemma one_inv : (1 : G)⁻¹ = 1 :=
begin
  sorry
end

@[simp] lemma inv_inv : a ⁻¹ ⁻¹ = a :=
begin
  sorry
end

/-

The reason I choose these five lemmas in particular, is that
term rewriting systems are very well understood by computer
scientists, and in particular there is something called the
Knuth-Bendix algorithm, which, given as input the three axioms
for a group which we used, produces a "confluent and noetherian
term rewrite system" that transforms every term into a unique
normal form. The system it produces is precisely the `simp`
lemmas which we haven proven above! See

https://en.wikipedia.org/wiki/Word_problem_(mathematics)#Example:_A_term_rewriting_system_to_decide_the_word_problem_in_the_free_group

for more information. I won't talk any more about the Knuth-Bendix
algorithm because it's really computer science, and I don't really
understand it, but apparently if you apply it to polynomial rings
then you get Buchberger's algorithm for computing Gröbner bases.

-/

-- Now let's try our example...
example : (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 := by simp -- short for begin simp end

-- The simplifier solves it!

-- try your own identities. `simp` will solve them all!

/-
This is everything I wanted to show you about groups and the simplifier today.
You can now either go on to subgroups in Part B, or practice your group
theory skills by proving the lemmas below.
-/

/-
We already proved `mul_eq_of_eq_inv_mul` but there are several other
similar-looking, but slightly different, versions of this. Here
is one.
-/

lemma eq_mul_inv_of_mul_eq {a b c : G} (h : a * c = b) : a = b * c⁻¹ :=
begin
  sorry
end

lemma eq_inv_mul_of_mul_eq {a b c : G} (h : b * a = c) : a = b⁻¹ * c :=
begin
  sorry
end

lemma mul_left_eq_self {a b : G} : a * b = b ↔ a = 1 :=
begin
  sorry
end

lemma mul_right_eq_self {a b : G} : a * b = a ↔ b = 1 :=
begin
  sorry
end

lemma eq_inv_of_mul_eq_one {a b : G} (h : a * b = 1) : a = b⁻¹ :=
begin
  sorry
end

lemma inv_eq_of_mul_eq_one {a b : G} (h : a * b = 1) : a⁻¹ = b :=
begin
  sorry,
end

lemma unique_left_id {e : G} (h : ∀ x : G, e * x = x) : e = 1 :=
begin
  sorry
end

lemma unique_right_inv {a b : G} (h : a * b = 1) : b = a⁻¹ :=
begin
  sorry
end

lemma mul_left_cancel_iff (a x y : G) : a * x = a * y ↔ x = y :=
begin
  split,
  { apply mul_left_cancel },
  { intro hxy,
    rwa hxy }
end

-- You don't even need to go into tactic mode (begin/end) to use `calc`:
lemma mul_right_cancel (a x y : G) (Habac : x * a = y * a) : x = y :=
calc x = x * 1 : by rw mul_one
  -- missing arguments here
  ... = y : by sorry

-- `↔` lemmas are good simp lemmas too.
@[simp] theorem inv_inj_iff {a b : G}: a⁻¹ = b⁻¹ ↔ a = b :=
begin
  sorry
end

theorem inv_eq {a b : G}: a⁻¹ = b ↔ b⁻¹ = a :=
begin
  sorry
end

end group

end xena


-- Eliminacion_de_la_disyuncion.lean
-- Eliminación de la disyunción.
-- José A. Alonso Jiménez
-- Sevilla, 13 de agosto de 2020
-- ---------------------------------------------------------------------

-- En este relación se muestra distintas formas de demostrar un teorema
-- con eliminación de la disyunción

-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería de tácticas.
-- 2. Declarar P, Q y R como variables sobre proposiciones.
-- ----------------------------------------------------------------------

import tactic              -- 1
variables (P Q R : Prop)   -- 2

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si
--    P → R y
--    Q → R
-- entonces
--    P ∨ Q → R
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example
  (hPR : P → R)
  (hQR : Q → R)
  : P ∨ Q → R :=
begin
  intro h,
  cases h with hP hQ,
  { exact hPR hP },
  { exact hQR hQ },
end

-- Comentario
-- ----------

-- La táctica (cases h with h1 h2), cuando la hipótesis h es una
-- disyunción aplica la regla de eliminación de la disyunción; es decir,
-- si h es (P ∨ Q), entonces elimina h y crea dos casos: uno añadiendo
-- la hipótesis (h1 : P)   y otro añadiendo la hipótesis (h2 : Q).

-- 2ª demostración
-- ===============

example
  (hPR : P → R)
  (hQR : Q → R)
  : P ∨ Q → R :=
begin
  rintro (hP | hQ),
  { exact hPR hP },
  { exact hQR hQ },
end

-- Comentario
-- ----------

-- La táctica (rintro (h1 | h2)), cuando la conclusión es una
-- implicación cuyo antecedente es una disyunción, aplica las regla des
-- introducción de la implicación y de eliminación de la disyunción; es
-- decir, si la conclusión es (P ∨ Q → R) entonces crea dos casos: en el
-- primero añade la hipótesis (h1 : P) y cambia a conclusión a R; en el
-- segundo añade la hipótesis (h2 : Q) y cambia la conclusión a R.

-- 3ª demostración
-- ===============

example
  (hPR : P → R)
  (hQR : Q → R)
  : P ∨ Q → R :=
λ h, or.elim h hPR hQR

-- Comentario: Se ha usado el lema
-- + or.elim : P ∨ Q → (P → R) → (Q → R) → R

-- 3ª demostración
-- ===============

example
  (hPR : P → R)
  (hQR : Q → R)
  : P ∨ Q → R :=
or.rec hPR hQR

-- Comentario: Se ha usado el lema
-- + or.rec    : (P → R) → (Q → R) → P ∨ Q → R

-- 4ª demostración
-- ===============

example
  (hPR : P → R)
  (hQR : Q → R)
  : P ∨ Q → R :=
by tauto


import analysis.specific_limits
import data.int.parity
import topology.sequences

attribute [instance] classical.prop_decidable

/-
Lemmas from that file were hidden in my course, or restating things which
were proved without name in previous files.
-/

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

-- The mathlib version is unusable because it is stated in terms of ≤
lemma ge_max_iff {α : Type*} [linear_order α] {p q r : α} : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q :=
max_le_iff

/- No idea why this is not in mathlib-/
lemma eq_of_abs_sub_le_all (x y : ℝ) : (∀ ε > 0, |x - y| ≤ ε) → x = y :=
begin
  intro h,
  apply eq_of_abs_sub_nonpos,
  by_contradiction H,
  push_neg at H,
  specialize h ( |x-y|/2) (by linarith),
  linarith,
end

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

lemma unique_limit {u l l'} : seq_limit u l → seq_limit u l' → l = l' :=
begin
  intros hl hl',
  apply eq_of_abs_sub_le_all,
  intros ε ε_pos,
  specialize hl (ε/2) (by linarith),
  cases hl with N hN,
  specialize hl' (ε/2) (by linarith),
  cases hl' with N' hN',
  specialize hN (max N N') (le_max_left _ _),
  specialize hN' (max N N') (le_max_right _ _),
  calc |l - l'| = |(l-u (max N N')) + (u (max N N') -l')| : by ring_nf
  ... ≤ |l - u (max N N')| + |u (max N N') - l'| : by apply abs_add
  ... = |u (max N N') - l| + |u (max N N') - l'| : by rw abs_sub_comm
  ... ≤ ε/2 + ε/2 : by linarith
  ... = ε : by ring,
end

lemma le_of_le_add_all {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  intro h,
  use (y-x)/2,
  split ; linarith,
end

def upper_bound (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x

def is_sup (A : set ℝ) (x : ℝ) := upper_bound A x ∧ ∀ y, upper_bound A y → x ≤ y

lemma lt_sup {A : set ℝ} {x : ℝ} (hx : is_sup A x) :
∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intro y,
  contrapose!,
  exact hx.right y,
end

lemma squeeze {u v w : ℕ → ℝ} {l} (hu : seq_limit u l) (hw : seq_limit w l)
(h : ∀ n, u n ≤ v n)
(h' : ∀ n, v n ≤ w n) : seq_limit v l :=
begin
  intros ε ε_pos,
  cases hu ε ε_pos with N hN,
  cases hw ε ε_pos with N' hN',
  use max N N',
  intros n hn,
  rw ge_max_iff at hn,
  specialize hN n (by linarith),
  specialize hN' n (by linarith),
  specialize h n,
  specialize h' n,
  rw abs_le at *,
  split ; linarith
end

def extraction (φ : ℕ → ℕ) := ∀ n m, n < m → φ n < φ m

def tendsto_infinity (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A

lemma lim_le {x y : ℝ} {u : ℕ → ℝ} (hu : seq_limit u x)
  (ineg : ∀ n, u n ≤ y) : x ≤ y :=
begin
  apply le_of_le_add_all,
  intros ε ε_pos,
  cases hu ε ε_pos with N hN,
  specialize hN N (by linarith),
  specialize ineg N,
  rw abs_le at hN,
  linarith,
end

lemma inv_succ_le_all :  ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε :=
begin
  convert metric.tendsto_at_top.mp (tendsto_one_div_add_at_top_nhds_0_nat),
  apply propext,
  simp only [real.dist_eq, sub_zero],
  split,
    intros h ε ε_pos,
    cases h (ε/2) (by linarith) with N hN,
    use N,
    intros n hn,
    rw abs_of_pos (nat.one_div_pos_of_nat : 1/(n+1 : ℝ) > 0),
    specialize hN n hn,
    linarith,
  intros h ε ε_pos,
  cases h ε (by linarith) with N hN,
  use N,
  intros n hn,
  specialize hN n hn,
  rw abs_of_pos (@nat.one_div_pos_of_nat ℝ _ n) at hN,
  linarith,
end

lemma limit_const (x : ℝ) : seq_limit (λ n, x) x :=
λ ε ε_pos, ⟨0, λ _ _, by simp [le_of_lt ε_pos]⟩

lemma limit_of_sub_le_inv_succ {u : ℕ → ℝ} {x : ℝ} (h : ∀ n, |u n - x| ≤ 1/(n+1)) :
seq_limit u x :=
begin
  intros ε ε_pos,
  rcases inv_succ_le_all ε ε_pos with ⟨N, hN⟩,
  use N,
  intros n hn,
  specialize h n,
  specialize hN n hn,
  linarith,
end

lemma limit_const_add_inv_succ (x : ℝ) : seq_limit (λ n, x + 1/(n+1)) x :=
limit_of_sub_le_inv_succ (λ n, by rw abs_of_pos ; linarith [@nat.one_div_pos_of_nat ℝ _ n])

lemma limit_const_sub_inv_succ (x : ℝ) : seq_limit (λ n, x - 1/(n+1)) x :=
begin
  refine limit_of_sub_le_inv_succ (λ n, _),
  rw [show x - 1 / (n + 1) - x = -(1/(n+1)), by ring, abs_neg,  abs_of_pos],
  linarith [@nat.one_div_pos_of_nat ℝ _ n]
end

lemma id_le_extraction {φ}: extraction φ → ∀ n, n ≤ φ n :=
begin
  intros hyp n,
  induction n with n hn,
  { exact nat.zero_le _ },
  { exact nat.succ_le_of_lt (by linarith [hyp n (n+1) (by linarith)]) },
end

lemma seq_limit_id : tendsto_infinity (λ n, n) :=
begin
  intros A,
  cases exists_nat_gt A with N hN,
  use N,
  intros n hn,
  have : (n : ℝ) ≥ N, exact_mod_cast hn,
  linarith,
end

variables {u : ℕ → ℝ} {l : ℝ} {φ : ℕ → ℕ}

open set filter

def cluster_point (u : ℕ → ℝ) (a : ℝ) :=
∃ φ, extraction φ ∧ seq_limit (u ∘ φ) a

lemma bolzano_weierstrass {a b : ℝ} {u : ℕ → ℝ} (h : ∀ n, u n ∈ Icc a b) :
∃ c ∈ Icc a b, cluster_point u c :=
begin
  rcases (is_compact_Icc : is_compact (Icc a b)).tendsto_subseq h with ⟨c, c_in, φ, hφ, lim⟩,
  use [c, c_in, φ, hφ],
  simp_rw [metric.tendsto_nhds, eventually_at_top, real.dist_eq] at lim,
  intros ε ε_pos,
  rcases lim ε ε_pos with ⟨N, hN⟩,
  use N,
  intros n hn,
  exact le_of_lt (hN n hn)
end

lemma not_seq_limit_of_tendstoinfinity {u : ℕ → ℝ} :
  tendsto_infinity u → ∀ x, ¬ seq_limit u x :=
begin
  intros lim_infinie x lim_x,
  cases lim_x 1 (by linarith) with N hN,
  cases lim_infinie (x+2) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  rw abs_le at hN,
  linarith,
end

open real

lemma sup_segment {a b : ℝ} {A : set ℝ} (hnonvide : ∃ x, x ∈ A) (h : A ⊆ Icc a b) :
  ∃ x ∈ Icc a b, is_sup A x :=
begin
  have b_maj :  ∀ (y : ℝ), y ∈ A → y ≤ b,
    from λ y y_in, (h y_in).2,
  have Sup_maj : upper_bound A (Sup A),
  { intro x,
    apply le_cSup,
    use [b, b_maj] } ,
  refine ⟨Sup A, _, _⟩,
  { split,
    { cases hnonvide with x x_in,
      exact le_trans (h x_in).1 (Sup_maj _ x_in) },
    { apply cSup_le hnonvide b_maj } },
  { exact ⟨Sup_maj, λ y, cSup_le hnonvide⟩ },
end

lemma subseq_tendsto_of_tendsto (h : seq_limit u l) (hφ : extraction φ) :
seq_limit (u ∘ φ) l :=
begin
  intros ε ε_pos,
  cases h ε ε_pos with N hN,
  use N,
  intros n hn,
  apply hN,
  calc N ≤ n   : hn
     ... ≤ φ n : id_le_extraction hφ n,
end
namespace tactic.interactive
open tactic

meta def check_me : tactic unit :=
`[ { repeat { unfold seq_limit},
   repeat { unfold continue_en },
   push_neg,
   try { simp only [exists_prop] },
   try { exact iff.rfl },
   done } <|> fail "That's not quite right. Please try again." ]

end tactic.interactive


import tactic
import number_theory.divisors -- added to make Bhavik's proof work

/-

# Find all integers x ≠ 3 such that x - 3 divides x^3 - 3

This is the second question in Sierpinski's book "250 elementary problems
in number theory".

my solution: x - 3 divides x^3-27, and hence if it divides x^3-3
then it also divides the difference, which is 24. Conversely,
if x-3 divides 24 then because it divides x^3-27 it also divides x^3-3

-/

example (x : ℤ) : x - 3 ∣ x^3 - 3 ↔ x - 3 ∣ 24 :=
begin
  have h : x-3∣x^3-27,
  { use x^2+3*x+9,
    ring, },
  split,
  { intro h1,
    have h2 := dvd_sub h1 h,
    convert h2,
    ring },
  { intro h1,
    convert dvd_add h h1,
    ring },
end

example (a : ℤ) : a ∣ 24 ↔ a ∈ ({-1,-2,-3,-4,-6,-8,-12,-24,1,2,3,4,6,8,12,24} : set ℤ) :=
begin
  split,
  { intro h,
    have h1 : a ≤ 24 := int.le_of_dvd (by norm_num) h,
    have h2 : -a ∣ 24,
    exact (neg_dvd a 24).mpr h,
    have h3 : -a ≤ 24 := int.le_of_dvd (by norm_num) h2,
    have h4 : -24 ≤ a := by linarith,
--    interval_cases a,
    sorry },
  { intro h,
    rcases h with (rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | h);
    try {norm_num},
    change a = 24 at h,
    rw h },
end

-- Thanks to Bhavik Mehta for showing me how to prove this in Lean
lemma int_dvd_iff (x : ℤ) (n : ℤ) (hn : n ≠ 0) :
  x ∣ n ↔ x.nat_abs ∈ n.nat_abs.divisors :=
by simp [hn]

example (x : ℤ) : x ∣ 24 ↔ x ∈ ({-1,-2,-3,-4,-6,-8,-12,-24,1,2,3,4,6,8,12,24} : set ℤ) :=
begin
  suffices : x ∣ 24 ↔ x.nat_abs ∈ ({1,2,3,4,6,8,12,24} : finset ℕ),
  { simp only [this, int.nat_abs_eq_iff, set.mem_insert_iff, set.mem_singleton_iff,
      finset.mem_insert, finset.mem_singleton],
    norm_cast,
    rw ←eq_iff_iff,
    ac_refl },
  exact int_dvd_iff _ 24 (by norm_num),
end


/- TACTICS -/

namespace TBA

-- definitions from last week
-- NOTE: We renamed it from `Nat'` for consistency. The new namespace makes sure we don't use the
-- standard library `Nat`.
inductive Nat : Type
  | zero : Nat
  | succ (n : Nat) : Nat

open Nat

def add (m n : Nat) : Nat :=
  match n with
  | zero   => m
  | succ n => succ (add m n)

-- With this command we add a notation for `add`. From now on we will be able to write `m + n` for
-- `add m n`. The 65 denotes how strongly the operator should bind to what's adjacent to it.
-- The `priority` means that Lean will prefer it over the built-in `+`.
infix:65 (priority := high) " + " => add

def mul (m n : Nat) : Nat :=
  match n with
  | zero   => zero
  | succ n => (mul m n) + m

-- We also want a notation for `mul`, with a higher binding strength than addition so that
-- `a + b * c` means `a + (b * c`)`.
infix:70 (priority := high) " * " => mul

inductive LE : Nat → Nat → Prop where
  | refl (n : Nat) : LE n n
  | succ : LE m n → LE m (succ n)

-- lower binding strength than either addition or multiplication
infix:50 (priority := high) " ≤ " => LE

-- Let's start by reproving some theorems from last week, but this time with tactics!
-- useful tactics:
-- * `induction ... with ...`
-- * `simp only [f]` to unfold applications of a function `f`
-- * `rw [h]` to rewrite every `a` to `b` if `h : a = b`
-- * `apply/exact`
-- * `simp/simp_all`... are powerful and basically always useful, though make sure that you could also
--   do the proof without them
theorem zero_add : zero + n = n := by
  induction n with
  | zero      =>
  | succ n ih =>

theorem le_add : m ≤ m + n := by

-- Alright, let's start automating more!
attribute [simp] add mul
-- These definitions will now automatically be unfolded when you use `simp/simp_all`

-- This one is a bit more tricky, you might need to prove a helper lemma!
theorem add_comm : n + m = m + n := by

-- Associativity can be proven in a similar way.
theorem add_assoc : (m + n) + k = m + (n + k) := by

def one := succ zero

theorem mul_one : m * one = m := by

-- To prove associativity of multiplication, you might have to come up with
-- some more lemmas about multiplication first. Some are similar to the above laws of
-- addition, some use both addition and multiplication ("distributivity" is the keyword).

theorem mul_assoc : (m * n) * k = m * (n * k) := by

-- Remember the structures for semigroups and monoids which we defined last week?
structure Semigroup (α : Type) where
  mul   : α → α → α
  assoc : mul (mul a b) c = mul a (mul b c)

structure Monoid (α : Type) extends Semigroup α where
  e     : α
  e_mul : mul e a = a
  mul_e : mul a e = a

-- You should now be able to instantiate two of them,  including proofs!
def Nat_add_Monoid : Monoid Nat := _

def Nat_mul_Monoid : Monoid Nat := _


import MyNat.Definition
import AdditionWorld.Level4 -- add_comm
import AdvancedAdditionWorld.Level5 -- add_right_cancel
namespace MyNat
open MyNat

/-!

# Advanced Addition World

## Level 6: `add_left_cancel`

The theorem `add_left_cancel` is the theorem that you can cancel on the left
when you're doing addition -- if `t + a = t + b` then `a = b`.
There is a three-line proof which ends in `exact add_right_cancel a t b` (or even
`exact add_right_cancel _ _ _`); this
strategy involves changing the goal to the statement of `add_right_cancel`.

## Theorem : add_left_cancel
On the set of natural numbers, addition has the left cancellation property.
In other words, if there are natural numbers `a, b` and `t` such that
if `t + a = t + b` then we have `a = b`.
-/
theorem add_left_cancel (t a b : MyNat) : t + a = t + b → a = b := by
  rw [add_comm]
  rw [add_comm t]
  exact add_right_cancel a t b

/-!
Next up [Level 7](./Level7.lean.md)
-/


import tuto_lib

section
/-
The first part of this file makes sure you can negate quantified statements
in your head without the help of `push_neg`.

You need to complete the statement and then use the `check_me` tactic
to check your answer. This tactic exists only for those exercises,
it mostly calls `push_neg` and then cleans up a bit.

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε
-/

-- In this section, u denotes a sequence of real numbers
-- f is a function from ℝ to ℝ
-- x₀ and l are real numbers
variables (u : ℕ → ℝ) (f : ℝ → ℝ) (x₀ l : ℝ)

/- Negation of "u tends to l" -/
-- 0062
example : ¬ (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε) ↔
  ∃ ε > 0, ∀ N, ∃ n ≥ N, |u n - l| > ε
:=
begin
  check_me,
end

/- Negation of "f is continuous at x₀" -/
-- 0063
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ →  |f x - f x₀| ≤ ε) ↔
  ∃ ε > 0, ∀ δ > 0, ∃ x, |x - x₀| ≤ δ ∧ |f x - f x₀| > ε
:=
begin
  check_me,
end

/-
In the next exercise, we need to keep in mind that
`∀ x x', ...` is the abbreviation of
`∀ x, ∀ x', ... `.

Also, `∃ x x', ...` is the abbreviation of `∃ x, ∃ x', ...`.
-/

/- Negation of "f is uniformly continuous on ℝ" -/
-- 0064
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x x', |x' - x| ≤ δ →  |f x' - f x| ≤ ε) ↔
  ∃ ε > 0, ∀ δ > 0, ∃ x x', |x' - x| ≤ δ ∧ |f x' - f x| > ε
:=
begin
  check_me,
end

/- Negation of "f is sequentially continuous at x₀" -/
-- 0065
example : ¬ (∀ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) → (∀ ε > 0, ∃ N, ∀ n ≥ N, |(f ∘ u) n - f x₀| ≤ ε))  ↔
  ∃ u : ℕ → ℝ,
    (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) ∧
    ∃ ε > 0, ∀ N, ∃ n ≥ N, |(f ∘ u) n - f x₀| > ε
:=
begin
  check_me,
end
end

/-
We now turn to elementary applications of negations to limits of sequences.
Remember that `linarith` can find easy numerical contradictions.

Also recall the following lemmas:

abs_le (x y : ℝ) : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y

ge_max_iff (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q

le_max_left p q : p ≤ max p q

le_max_right p q : q ≤ max p q

/-- The sequence `u` tends to `+∞`. -/
def tendsto_infinity (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A
-/

-- 0066
example {u : ℕ → ℝ} : tendsto_infinity u → ∀ l, ¬ seq_limit u l :=
begin
  intros tend l lim,
  cases lim 1 (by linarith) with nlim hlim,
  cases tend (l+2) with ntend htend,
  let n := max nlim ntend,
  specialize hlim n (le_max_left _ _),
  specialize htend n (le_max_right _ _),
  rw abs_le at hlim,
  linarith,
end

def nondecreasing_seq (u : ℕ → ℝ) := ∀ n m, n ≤ m → u n ≤ u m

-- 0067
example (u : ℕ → ℝ) (l : ℝ) (h : seq_limit u l) (h' : nondecreasing_seq u) :
  ∀ n, u n ≤ l :=
begin
  intro n,
  by_contradiction hc,
  cases h ((u n - l)/2) (by linarith) with nlim hlim,
  let n' := max n nlim,
  specialize hlim n' (le_max_right _ _),
  specialize h' n n' (le_max_left _ _),
  rw abs_le at hlim,
  linarith,
end

/-
In the following exercises, `A : set ℝ` means that A is a set of real numbers.
We can use the usual notation x ∈ A.

The notation `∀ x ∈ A, ...` is the abbreviation of `∀ x, x ∈ A → ... `

The notation `∃ x ∈ A, ...` is the abbreviation of `∃ x, x ∈ A ∧ ... `.
More precisely it is the abbreviation of `∃ x (H : x ∈ A), ...`
which is Lean's strange way of saying `∃ x, x ∈ A ∧ ... `.
You can convert between these forms using the lemma
  exists_prop {p q : Prop} : (∃ (h : p), q) ↔ p ∧ q

We'll work with upper bounds and supremums.
Again we'll introduce specialized definitions for the sake of exercises, but mathlib
has more general versions.

def upper_bound (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x

def is_sup (A : set ℝ) (x : ℝ) := upper_bound A x ∧ ∀ y, upper_bound A y → x ≤ y

Remark: one can easily show that a set of real numbers has at most one sup,
but we won't need this.
-/

-- 0068
example {A : set ℝ} {x : ℝ} (hx : is_sup A x) :
∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intro y,
  contrapose!,
  exact hx.right y,
end

/-
Let's do a variation on an example from file 07 that will be useful in the last
exercise below.
-/

-- 0069
lemma le_of_le_add_all' {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  intro h,
  use (y - x) / 2,
  split; linarith,
end

-- 0070
example {x y : ℝ} {u : ℕ → ℝ} (hu : seq_limit u x)
  (ineg : ∀ n, u n ≤ y) : x ≤ y :=
begin
  apply le_of_le_add_all',
  intros ε ε_pos,
  cases hu ε ε_pos with n hn,
  specialize hn n (by linarith),
  rw abs_le at hn,
  linarith [ineg n],
end


variables AA AB AP MA MB MP CA CB CP : Prop

--estas variáveis são suposições naturais.
--Existem várias outras, mas não são necessárias no momento.
variables (h1 : AP ↔ ¬ AA ∧ ¬ AB)
          (h2 : AA ↔ ¬ AB ∧ ¬ AP)
          (h3 : AB ↔ ¬ AA ∧ ¬ AP)
          (h4 : MB ↔ ¬ AB ∧ ¬ CB)
          (h5 : MA ↔ (AP ∧ CB) ∨ (AB ∧ CP))

-- estas variáveis são as falas, vistas do ponto de Ana.
-- Como Ana sempre diz a verdade, todas essas afirmações são verdadeiras.
-- Considerar as falas de Maria não faz sentido do ponto de vista lógico.
variables (f1: AA → AB)
          (f2: AB → MB)
          (f3: AP → CB)

def triple_and(A B C: Prop): Prop :=
(A ∧ B) ∧ C

example : triple_and AP CB MA :=  -- quero provar que Ana usa preto,
                                  -- Cláudia usa branco e
                                  -- Maria usa azul
have  a1: ¬ AA, from
   assume a2: AA,
   have a3: AB, from f1 a2,
   have a4: ¬AA, from and.left (iff.elim_left h3 a3),
   show false, from a4 a2,

have a5: ¬ AB, from
  assume a6: AB,
  have a7: MB, from f2 a6,
  have a8: ¬ AB, from and.left (iff.elim_left h4 a7),
  show false, from a8 a6,

have a9: AP, from iff.elim_right h1 (and.intro a1 a5),

have c1: CB, from f3 a9,

have u1: AP ∧ CB, from and.intro a9 c1,

have m1: MA, from iff.elim_right h5 (or.inl u1),

show (AP ∧ CB) ∧ MA, from and.intro u1 m1


/-
Copyright (c) 2022 Yaël Dillies, Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies, Bhavik Mehta
-/
import algebra.big_operators.order
import combinatorics.simple_graph.density

/-!
# Energy of a partition

This file defines the energy of a partition.

The energy is the auxiliary quantity that drives the induction process in the proof of Szemerédi's
Regularity Lemma. As long as we do not have a suitable equipartition, we will find a new one that
has an energy greater than the previous one plus some fixed constant.
-/

open finset
open_locale big_operators

variables {α : Type*} [decidable_eq α] {s : finset α} (P : finpartition s) (G : simple_graph α)
  [decidable_rel G.adj]

namespace finpartition

/-- The energy of a partition, also known as index. Auxiliary quantity for Szemerédi's regularity
lemma.  -/
def energy : ℚ := (∑ uv in P.parts.off_diag, G.edge_density uv.1 uv.2 ^ 2) / P.parts.card ^ 2

lemma energy_nonneg : 0 ≤ P.energy G :=
div_nonneg (finset.sum_nonneg $ λ _ _, sq_nonneg _) $ sq_nonneg _

lemma energy_le_one : P.energy G ≤ 1 :=
div_le_of_nonneg_of_le_mul (sq_nonneg _) zero_le_one $
  calc ∑ uv in P.parts.off_diag, G.edge_density uv.1 uv.2^2
        ≤ P.parts.off_diag.card • 1
        : sum_le_card_nsmul _ _ 1 $ λ uv _, (sq_le_one_iff $ G.edge_density_nonneg _ _).2 $
            G.edge_density_le_one _ _
    ... = P.parts.off_diag.card : nat.smul_one_eq_coe _
    ... ≤ _ : by { rw [off_diag_card, one_mul, ←nat.cast_pow, nat.cast_le, sq], exact tsub_le_self }

end finpartition


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson
-/
import analysis.special_functions.trigonometric.complex
import analysis.special_functions.trigonometric.deriv

/-!
# Complex trigonometric functions

Basic facts and derivatives for the complex trigonometric functions.
-/

noncomputable theory

namespace complex

open set filter
open_locale real

lemma has_strict_deriv_at_tan {x : ℂ} (h : cos x ≠ 0) :
  has_strict_deriv_at tan (1 / (cos x)^2) x :=
begin
  convert (has_strict_deriv_at_sin x).div (has_strict_deriv_at_cos x) h,
  rw ← sin_sq_add_cos_sq x,
  ring,
end

lemma has_deriv_at_tan {x : ℂ} (h : cos x ≠ 0) :
  has_deriv_at tan (1 / (cos x)^2) x :=
(has_strict_deriv_at_tan h).has_deriv_at

open_locale topological_space

lemma tendsto_abs_tan_of_cos_eq_zero {x : ℂ} (hx : cos x = 0) :
  tendsto (λ x, abs (tan x)) (𝓝[{x}ᶜ] x) at_top :=
begin
  simp only [tan_eq_sin_div_cos, ← norm_eq_abs, normed_field.norm_div],
  have A : sin x ≠ 0 := λ h, by simpa [*, sq] using sin_sq_add_cos_sq x,
  have B : tendsto cos (𝓝[{x}ᶜ] (x)) (𝓝[{0}ᶜ] 0),
  { refine tendsto_inf.2 ⟨tendsto.mono_left _ inf_le_left, tendsto_principal.2 _⟩,
    exacts [continuous_cos.tendsto' x 0 hx,
      hx ▸ (has_deriv_at_cos _).eventually_ne (neg_ne_zero.2 A)] },
  exact continuous_sin.continuous_within_at.norm.mul_at_top (norm_pos_iff.2 A)
    (tendsto_norm_nhds_within_zero.comp B).inv_tendsto_zero,
end

lemma tendsto_abs_tan_at_top (k : ℤ) :
  tendsto (λ x, abs (tan x)) (𝓝[{(2 * k + 1) * π / 2}ᶜ] ((2 * k + 1) * π / 2)) at_top :=
tendsto_abs_tan_of_cos_eq_zero $ cos_eq_zero_iff.2 ⟨k, rfl⟩

@[simp] lemma continuous_at_tan {x : ℂ} : continuous_at tan x ↔ cos x ≠ 0 :=
begin
  refine ⟨λ hc h₀, _, λ h, (has_deriv_at_tan h).continuous_at⟩,
  exact not_tendsto_nhds_of_tendsto_at_top (tendsto_abs_tan_of_cos_eq_zero h₀) _
    (hc.norm.tendsto.mono_left inf_le_left)
end

@[simp] lemma differentiable_at_tan {x : ℂ} : differentiable_at ℂ tan x ↔ cos x ≠ 0 :=
⟨λ h, continuous_at_tan.1 h.continuous_at, λ h, (has_deriv_at_tan h).differentiable_at⟩

@[simp] lemma deriv_tan (x : ℂ) : deriv tan x = 1 / (cos x)^2 :=
if h : cos x = 0 then
  have ¬differentiable_at ℂ tan x := mt differentiable_at_tan.1 (not_not.2 h),
  by simp [deriv_zero_of_not_differentiable_at this, h, sq]
else (has_deriv_at_tan h).deriv

@[simp] lemma times_cont_diff_at_tan {x : ℂ} {n : with_top ℕ} :
  times_cont_diff_at ℂ n tan x ↔ cos x ≠ 0 :=
⟨λ h, continuous_at_tan.1 h.continuous_at,
  times_cont_diff_sin.times_cont_diff_at.div times_cont_diff_cos.times_cont_diff_at⟩

end complex


/- AND
-- X, Y ⊢ X ∧ Y              -- and introduction
-- X ∧ Y ⊢ X                 -- and elimination left
-- X ∧ Y ⊢ Y                 -- and elimination right
-/
#check @and.intro       -- ∀ {a b : Prop}, a → b → a ∧ b
#check @and.elim_left   -- ∀ {a b : Prop}, a ∧ b → a
#check @and.elim_right  -- ∀ {a b : Prop}, a ∧ b → b

/- OR
-- X ⊢ X ∨ Y                -- or introduction left
-- Y ⊢ X ∨ Y                -- or introduction right
-- X ∨ Y, X → Z, Y → Z ⊢ Z  -- or elimination
-/
#check @or.inl          -- ∀ {a b : Prop}, a → a ∨ b
#check @or.inr          -- ∀ {a b : Prop}, b → a ∨ b
#check @or.intro_left   -- ∀ {a : Prop} (b : Prop), a → a ∨ b
#check @or.intro_right  -- ∀ (a : Prop) {b : Prop}, b → a ∨ b
#check @or.elim         -- ∀ {a b c : Prop}, a ∨ b → (a → c) → (b → c) → c

/-
Use inl or inr when Lean can infer the other disjunct,
otherwise use intro_left or intro_right, where you have
to give the other disjunct (a proposition, not a proof)
as an explicit argument.
-/

/- FORALL, ARROW
Arrow is a special case of ∀ in Lean, and ∀ is built
into the very core of the logic. There are no rules
called forall.intro or forall.elim. Rather, you prove
a forall or arrow proposition by *defining a function*
that takes a value/proof of the premise/argument and
that returns a value/proof of the conclusion. On the
other hand, forall/arrow elimination is by *applying*
such a "function/proof" to an argument of the right
kind to obtain a result of the right kind.

Insight: Whenever you define a function (in Python,
for example), you declare formal parameters (arguments)
and then, within the body of the function, you *assume*
that you've been given actual parameter values of the
right types, and then you write a that constructs and
returns a result of the right type . That is exactly
what you're doing when you use forall/arrow introduction:
assume you've been given actual arguments of the right
types then  show that you can construct and return a
value of the specified "return" type. This works in
Lean whether the types and values you're working with
are computational (numbers, strings, etc) or logical
(propositions, proofs).
-/

/-
There are several formats for writing functions in
Lean. Let's start with a computational example: a
simple function that takes a string, str, and returns
its length. The following four definitions are all
equivalent!
-/

-- use → introduction
def slength1 : string → nat :=
begin
assume (str : string),  -- assume given s of type string
exact (str.length)      -- defined in Lean's libraries
end

def slength2 : string → nat
| str := str.length     -- matches str to first argument

-- ∀ introduction
def slength3 : ∀ (s : string), nat :=
begin
intro str,              -- assume given (str : string)
exact (str.length)      -- defined in Lean's libraries
end

-- almost ordinary function definition
-- argument declared/named *before* the color
def slength4 (str : string) : nat :=
begin
  exact str.length
end

-- Python/C++ style
def slength5 (str : string) : nat := str.length

-- ∀/→ introduction is function definition
-- ∀/→ elimination is function application

#eval slength1 "Lean is actually very cool!"
#eval slength2 "Lean is actually very cool!"
#eval slength3 "Lean is actually very cool!"
#eval slength4 "Lean is actually very cool!"
#eval slength5 "Lean is actually very cool!"

/-
And of course it works for logical types and
proofs just as well as for data/computational
types and ordinary data values. Here are three
ways to prove that any proposition P implies
true, for example.
-/

-- Using →
lemma foo0 : Prop → true :=
begin
  assume (P : Prop),  -- bind name to assume argument
  exact true.intro    -- return result of right type (true)
end

lemma foo1 : Prop → true
| P := true.intro

-- Using ∀
lemma foo2 : ∀ (P : Prop), true :=
begin
intro P,
exact true.intro,
end

-- Python/C style but using script to generate proof "term"
lemma foo3 (P : Prop) : true :=
begin
exact true.intro
end

-- Python/C style returning exact proof term
lemma foo4 (P : Prop) : true := true.intro

-- Each function is a proof of Prop → true
#check foo0
#check foo1
#check foo2
#check foo3

/- NOT
-- ¬X means (X → false)   -- negation introduction
-- ¬¬X ⊢ X                -- negation elimination
-/
-- introduction: to prove ¬X, use → introduction
-- (double) ¬ elimination is proof by contradiction
#check @classical.by_contradiction  -- ∀ {p : Prop}, (¬p → false) → p

/- IFF
-- X → Y, Y → X ⊢ X ↔ Y     -- iff introduction
-- X ↔ Y ⊢ X → Y            -- iff elimination left
-- X ↔ Y ⊢ Y → X            -- iff elimination right
-/
#check @iff.intro     -- ∀ {a b : Prop}, (a → b) → (b → a) → (a ↔ b)
#check @iff.mp        -- ∀ {a b : Prop}, (a ↔ b) → a → b
#check @iff.mpr       -- ∀ {a b : Prop}, (a ↔ b) → b → a

/- EXISTS

The one remaining set of inference rules we have
to cover are for ∃. We will cover them after the
mid-term.
-/

/-
Yay! Except for exists, you have now learned *all* of the
inference rules of higher-order predicate logic in Lean,
which are all also inference rules in classical first-order
logic.  Yay, again! Good work.
-/


import algebra.big_operators data.fintype
import tactic.ring

-- https://xenaproject.wordpress.com/2018/03/30/proofs-by-induction/

open nat

def odd : ℕ → ℕ := λ i, 2 * i + 1
def square : ℕ → ℕ := λ i, i ^ 2
theorem odd_square_inductive_step (d : ℕ) :
  square d + odd d = square (succ d) :=
by dsimp [square, odd, pow]; rw [succ_eq_add_one]; ring

namespace def1

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ
| 0 := 0
| (succ n) := my_sum_to_n n + summand n

theorem my_zero_theorem (summand : ℕ → ℕ) :
  my_sum_to_n summand 0 = 0 :=
rfl

theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (succ n) = my_sum_to_n summand n + summand n :=
rfl

theorem my_odd_square_theorem : ∀ (n : ℕ), my_sum_to_n odd n = square n
| 0        := rfl
| (succ n) := by unfold my_sum_to_n; rw [my_odd_square_theorem n]; exact odd_square_inductive_step n

end def1

namespace def2

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
λ n, ((list.range n).map summand).sum

theorem my_zero_theorem (summand : ℕ → ℕ) :
  my_sum_to_n summand 0 = 0 :=
rfl

theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (succ n) = my_sum_to_n summand n + summand n :=
by unfold my_sum_to_n; simp [list.range_concat]

theorem my_odd_square_theorem : ∀ (n : ℕ), my_sum_to_n odd n = square n
| 0        := rfl
| (succ n) := by rw [my_successor_theorem, my_odd_square_theorem n]; exact odd_square_inductive_step n

end def2

namespace def3

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
λ n, (finset.range n).sum summand

theorem my_zero_theorem (summand : ℕ → ℕ) :
  my_sum_to_n summand 0 = 0 :=
rfl

theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (succ n) = my_sum_to_n summand n + summand n :=
by unfold my_sum_to_n; simp

theorem my_odd_square_theorem : ∀ (n : ℕ), my_sum_to_n odd n = square n
| 0        := rfl
| (succ n) := by rw [my_successor_theorem, my_odd_square_theorem n]; exact odd_square_inductive_step n

end def3

namespace def4

open finset nat

-- Credits to Chris Hughes
theorem chris (n : ℕ) (f : ℕ → ℕ) (g : fin n → ℕ) (h : ∀ i : fin n, f i.1  = g i) :
    (range n).sum f = univ.sum g :=
sum_bij
  (λ i h, ⟨i, mem_range.1 h⟩)
  (λ i h, mem_univ _)
  (λ a ha, h ⟨a, _⟩)
  (λ _ _ _ _, fin.veq_of_eq)
  (λ ⟨b, hb⟩ _, ⟨b, mem_range.2 hb, rfl⟩)

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
λ n, (@finset.univ (fin n) _).sum (summand ∘ fin.val)

theorem my_zero_theorem (summand : ℕ → ℕ) :
  my_sum_to_n summand 0 = 0 :=
rfl

-- Credits to Mario Carneiro
theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (succ n) = my_sum_to_n summand n + summand n :=
by unfold my_sum_to_n;
rw [← chris _ _ _ (λ _, rfl), ← chris _ _ _ (λ _, rfl)]; simp

theorem my_odd_square_theorem : ∀ (n : ℕ), my_sum_to_n odd n = square n
| 0        := rfl
| (succ n) := by rw [my_successor_theorem, my_odd_square_theorem n]; exact odd_square_inductive_step n

end def4

namespace equality

theorem def12 : def1.my_sum_to_n = def2.my_sum_to_n :=
funext $ λ summand, funext $ λ n, nat.rec_on n rfl $
λ m ih, by rw [def1.my_successor_theorem, def2.my_successor_theorem, ih]

theorem def23 : def2.my_sum_to_n = def3.my_sum_to_n :=
funext $ λ summand, funext $ λ n, nat.rec_on n rfl $
λ m ih, by rw [def2.my_successor_theorem, def3.my_successor_theorem, ih]

theorem def34 : def3.my_sum_to_n = def4.my_sum_to_n :=
funext $ λ summand, funext $ λ n, nat.rec_on n rfl $
λ m ih, by rw [def3.my_successor_theorem, def4.my_successor_theorem, ih]

end equality


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

! This file was ported from Lean 3 source module data.nat.periodic
! leanprover-community/mathlib commit dc6c365e751e34d100e80fe6e314c3c3e0fd2988
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Periodic
import Mathlib.Data.Nat.Count
import Mathlib.Data.Nat.Interval

/-!
# Periodic Functions on ℕ

This file identifies a few functions on `ℕ` which are periodic, and also proves a lemma about
periodic predicates which helps determine their cardinality when filtering intervals over them.
-/

namespace Nat

open Nat Function

theorem periodic_gcd (a : ℕ) : Periodic (gcd a) a := by
  simp only [forall_const, gcd_add_self_right, eq_self_iff_true, Periodic]
#align nat.periodic_gcd Nat.periodic_gcd

theorem periodic_coprime (a : ℕ) : Periodic (coprime a) a := by
  simp only [coprime_add_self_right, forall_const, iff_self_iff, eq_iff_iff, Periodic]
#align nat.periodic_coprime Nat.periodic_coprime

theorem periodic_mod (a : ℕ) : Periodic (fun n => n % a) a := by
  simp only [forall_const, eq_self_iff_true, add_mod_right, Periodic]
#align nat.periodic_mod Nat.periodic_mod

theorem Function.Periodic.map_mod_nat {α : Type _} {f : ℕ → α} {a : ℕ} (hf : Periodic f a) :
    ∀ n, f (n % a) = f n := fun n => by
  conv_rhs => rw [← Nat.mod_add_div n a, mul_comm, ← Nat.nsmul_eq_mul, hf.nsmul]
#align function.periodic.map_mod_nat Nat.Function.Periodic.map_mod_nat

section Multiset

open Multiset

/-- An interval of length `a` filtered over a periodic predicate of period `a` has cardinality
equal to the number naturals below `a` for which `p a` is true. -/
theorem filter_multiset_Ico_card_eq_of_periodic (n a : ℕ) (p : ℕ → Prop) [DecidablePred p]
    (pp : Periodic p a) : card (filter p (Ico n (n + a))) = a.count p := by
  rw [count_eq_card_filter_range, Finset.card, Finset.filter_val, Finset.range_val, ←
    multiset_Ico_map_mod n, ← map_count_True_eq_filter_card, ← map_count_True_eq_filter_card,
    map_map]
  congr; funext n
  exact (Function.Periodic.map_mod_nat pp n).symm
#align nat.filter_multiset_Ico_card_eq_of_periodic Nat.filter_multiset_Ico_card_eq_of_periodic

end Multiset

section Finset

open Finset

/-- An interval of length `a` filtered over a periodic predicate of period `a` has cardinality
equal to the number naturals below `a` for which `p a` is true. -/
theorem filter_Ico_card_eq_of_periodic (n a : ℕ) (p : ℕ → Prop) [DecidablePred p]
    (pp : Periodic p a) : ((Ico n (n + a)).filter p).card = a.count p :=
  filter_multiset_Ico_card_eq_of_periodic n a p pp
#align nat.filter_Ico_card_eq_of_periodic Nat.filter_Ico_card_eq_of_periodic

end Finset

end Nat


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

import representation_theory.basic

/-

# Representation theory

There are two ways to do representation theory -- the way you might guess
(involving vector spaces `(V : Type) [add_comm_group V] [module k V]`) and
a funkier way using category theory. Let's start with the vector space way.

## `representation k G V`

Let `k` be a field (or a commutative ring), let `G` be a group (or a monoid)
and let `V` be a vector space (or module) over `k`.

Then `representation k G V` is the type of representations of `G` on `V`,
where `G` acts `k`-linearly. It's not a class, because one group `G` can act
in more than one way on a vector space `V`.

-/

-- Let k be a field, G a group and V a k-vector space
variables (k : Type) [field k] (G : Type) [group G]
  (V : Type) [add_comm_group V] [module k V]

-- Let `ρ` be a representation of `G` on `V`.
variable (ρ : representation k G V)

-- If `g : G` and `v : V` then `ρ(g)v` is done like this:

variables (g : G) (v : V)

example : V := ρ g v

-- Here are the names of some basic lemmas

variables (h : G) (w : V) (c : k)

example : ρ g (v + w) = ρ g v + ρ g w := map_add (ρ g) v w
example : ρ g (c • w) = c • ρ g w := (ρ g).map_smul c w

-- I was quite surprised to find that this doesn't seem to be in the library
example : ρ (g * h) v = ρ g (ρ h v) :=
begin
  rw map_mul,
  rw linear_map.mul_apply,
end

/- Let's talk about this previous proof. `map_mul` is the statement that if `f`
is a group homomorphism then `f(g*h)=f(g)*f(h)`. What is happening is that
the definition of `representation` is this:

`abbreviation representation := G →* (V →ₗ[k] V)`

What's going on here? `V →ₗ[k] V` is the type of `k`-linear maps from `V` to `V`.
This space has a multiplication, given by function composition (composite of two
linear maps is a linear map). This doesn't make it a group! Maps like the zero map
have no inverse (if dim(V)>0). But it does have an identity, namely the
identity map from `V` to `V`. So it is a monoid, which is a "group without inverses".
And `G →* (V →ₗ[k] V)` means the `*`-preserving maps from `G` to `V →ₗ[k] V`, or
the monoid homomorphisms if you'd rather, so it's maps `ρ` satisfying `ρ(gh)=ρ(g)*ρ(h)`,
or in other words `ρ(gh)=ρ(g)∘ρ(h)`. If two functions are equal, then they take the
same values everywhere, so we deduce that for all `v`, `ρ(gh)(v)=ρ(g)(ρ(h)v)`, and
that should be what the proof looks like in Lean.

If `representation` were defined as a `def` then this proof wouldn't work, because
`representation k G V` would be definitionally, but not syntactically, `G →* (V →ₗ[k] V)`,
and the `rw` tactic works up to syntactic equality. One would have to start with
the line `unfold representation` to get `rw map_mul` to work. But because it's
defined as an `abbreviation`, this means that Lean *does* unfold it internally.
Lean has three kinds of "reducibility settings" for definitions. There is `irreducible`,
which means "never unfold", there is `semireducible`, which is the default, and so
the behaviour that you're used to, and there is `reducible`, which basically means
"unfold the moment you think it might help". Making a definition with `abbreviation`
makes it `reducible`. So the rewrite works anyway.

Note that the proof of `linear_map.mul_apply` is `refl`, because `f ∘ g` is *defined*
to mean `λ x, f (g x)`, so `(f ∘ g) (x) = f (g x)` is definitionally true. So this
proof works as well:

-/

example : ρ (g * h) v = ρ g (ρ h v) :=
begin
  rw map_mul,
  -- ⊢ `⇑(⇑ρ g * ⇑ρ h) v = ⇑(⇑ρ g) (⇑(⇑ρ h) v)`
  -- and note `ρ g * ρ h` is definitionally `ρ g ∘ ρ h`,
  -- which is definitionally `λ v, (ρ g) (ρ h v)`. So this works:
  refl,
end

-- Let's make a representation! Because internally it's a group homomorphism,
-- which is a structure, we just have to fill in the fields. Let's start with
-- the trivial representation.

example : representation k G V :=
{ to_fun := λ g, 1,
  map_one' := rfl,
  map_mul' := λ g h, by simp }

-- If we have a group homomorphism `χ : G → kˣ` then we can make a representation
-- of `G` on `V` by defining ρ(g)v=χ(g)•v.

example (χ : G →* kˣ) : representation k G V :=
{ to_fun := λ g,
  { to_fun := λ v, (χ g) • v,
    map_add' := smul_add (χ g),
    map_smul' := begin
      intros,
      simp only [smul_comm (χ g) r x, ring_hom.id_apply],
    end },
  map_one' := begin
    ext v,
    simp,
  end,
  map_mul' := begin
    intros g h,
    ext v,
    dsimp,
    simp only [map_mul, mul_smul],
  end }

/- If `G` is finite then `G → k` is a vector space with a natural basis indexed by `G`:
   for `g : G` define the function `δᵍ : G → k` sending `g` to `1` and everything else to `0`,
   and then the `δᵍ` are a basis. Now `G` acts naturally on these basis elements
   and hence gives us a representation of `G` on this space. Note that I've done the
   typical mathematician's thing of leaving out some of the details. See if you can
   work them out. -/

  example : representation k G (G → k) :=
  { to_fun := λ g,
    { to_fun := λ f h, f (h * g),
      map_add' := λ φ ψ, begin
        ext j,
        refl,
      end,
      map_smul' := λ r f, begin
        ext h,
        simp,
      end },
  map_one' := begin
    intros, ext, simp,
  end,
  map_mul' := begin
    intros g h,
    ext f j,
    simp [mul_assoc],
  end }


import definitions
import point3

-- 2F(m+n) = F(m) L(n) + L(m) F(n)
lemma two_mul_Fib_add (m n : ℤ) : 2 * Fib (m + n) = Fib m * Luc n + Luc m * Fib n :=
by rw [Fib, Fib, Fib, Luc, Luc, gpow_add, units.coe_mul, Zalpha.mul_r, two_mul];
  rw [α_Fib, α_Fib, β_Fib, β_Fib];
  have Hm := Fib_add_two (m-1);
  have Hn := Fib_add_two (n-1);
  rw [bit0, ← add_assoc, sub_add_cancel] at Hm Hn;
  simp [Hm, Hn, mul_add, add_mul]

lemma two_mul_fib_add (m n : ℕ) : 2 * fib (m + n) = fib m * luc n + luc m * fib n :=
int.coe_nat_inj $ by rw [int.coe_nat_add, int.coe_nat_mul, int.coe_nat_mul, int.coe_nat_mul];
  rw [← fib_down, ← fib_down, ← fib_down, ← luc_down, ← luc_down, ← two_mul_Fib_add]; refl

-- L(4n) + 2 = L(2n)^2
lemma Luc_four_mul (n : ℤ) : Luc (4 * n) = Luc (2 * n) * Luc (2 * n) - 2 :=
Zalpha.of_int_inj.1 $ by have := Luc_αβ; simp at this;
  simp [this, add_mul, mul_add];
  rw [← units.coe_mul, ← units.coe_mul, ← units.coe_mul, ← units.coe_mul];
  rw [← mul_gpow, ← mul_gpow, ← mul_gpow, ← mul_gpow]; simp;
  rw [gpow_mul (-1 : units ℤα)];
  have : (-1 : units ℤα)^(2:ℤ) = 1 := rfl;
  simp [this]; rw [bit0, add_mul, gpow_add, gpow_add, mul_gpow, mul_gpow]; ring

lemma luc_four_mul (n : ℕ) : luc (4 * n) + 2 = luc (2 * n) * luc (2 * n) :=
int.coe_nat_inj $ begin
  simp, rw [← luc_down, ← luc_down],
  change (2 : ℤ) + Luc (4 * n) = _,
  rw [Luc_four_mul]; ring end

-- F(2n) = F(n) L(n)
lemma fib_two_mul (n : ℕ) : fib (2 * n) = fib n * luc n :=
nat.bit0_inj $ by rw [bit0, ← two_mul, two_mul n, two_mul_fib_add, mul_comm]; refl

-- L(2n) ∣ 2(F(m+4n) + F(m))
lemma luc_two_mul_dvd (m n : ℕ) : luc (2 * n) ∣ 2 * (fib (m + 4 * n) + fib m) :=
⟨luc (2 * n) * fib m + luc m * fib (2 * n),
by rw [mul_add, mul_add, two_mul_fib_add, mul_comm (fib m), add_right_comm, ← add_mul, luc_four_mul];
conv in (4 * n) { change ((2 * 2) * n) };
rw [mul_assoc 2, fib_two_mul]; ac_refl⟩

-- TODO: if ¬(3 ∣ n) then fib (m + 4*n) ≡ −fib m [MOD (luc (2*n))]