import data.set

/- #1

Prove that if there's an object, a, of some type, α,
having some property (satisfying a predicate), P, then
not every object of type α fails to have property, P.
-/

example (α : Type) (P : α → Prop) : (∃ a, P a) → (¬(∀ x, ¬ P x)) :=
begin
assume h,
cases h with w pw,
assume k,
have npw := k w,
contradiction,
end

/- Extra credit.

The converse of this proposition is clasically true. If
not every object has propery, P, then there must be some
object that has it. If you try to prove the converse in
our constructive logic, what happens? Show you work, and
then briefly but clearly explain exactly what goes wrong.
-/

/- #2

Consider the following binary relation, r, with domain
and the co-domain both being ℕ.

( domain = ℕ, r = {(0,0),(1,1),(2,2)}, co-domain=ℕ )

A. Is this relation reflexive? Explain your answer.

No. Not *every* object of the domain is related to itself.

B. Is this relation symmetric? Explain your answer.

Yes, the reverse of every pair in the relation is also
in the relation.

C. Is this relation transitive? Explain your answer.

Yes. In all cases (there are none!) in which an a is
related to a b, and a b is related to a c that a is
related to that c. Again there are *no* such cases, so,
again, the it's true for all of them!

D. Is this relation an equivalence relation? Explain.

No. It's not reflexive.

-/

/- #3

A binary relation, r, is said to be *anti-symetric*
if, for all values in its domain, a and b, if r a b
and if r b a then a = b. Give an example of a familiar
arithmetic relation that's anti-symmetric, and briefly
explain why it's so.

Answer: less than or equals is an anti-symmetric
relation. If a <= b and b <= a then it must be that
case that a = b.
-/

/- #4
A binary relation, r, is said to be asymmetric if
whenever, for any a and b, if r a b then ¬ r b a.
-/

/- A.

First, express this idea formally by completing the
following definition.
-/

def is_asymmetric
  {α : Type}
  (r : α → α → Prop) :
  Prop
  :=
∀ (a b : α), r a b → ¬ r b a

/- B.

Name a familar arithmetic relation that's asymmetric
and briefly explain why you think it's asymmetric.

Answer here: The less than relation (on natural numbers)
is asymmetric because for any a, b, if a < b then ¬ b < a.

-/

/- C:

An object cannot be related to itself in an asymmetric
relation. First, complete the following informal proof
of this statement.

Proof: Assume α, r, and a are as given (and in particular
assume that r is asymmetric). Now assume r a a.

Answer here (rest of proof): From the fact that r is
asymmetric and given the assumption that r a a, we can
conclude ¬ r a a. That's is a contradiction. The r a a
cannot have been true. Since a was arbitrary, no a can
be related to itself.
-/

/- D.

Now prove a closely related proposition formally.
Add a comment to each line of your formal proof
so as to construct a good skeleton for a fluent
English language proof.
-/

example
  (α : Type)
  (r : α → α → Prop)
  (h : is_asymmetric r) :
¬ ∃ (a : α), r a a :=
begin
-- proof by negation
-- suppose there is such an a
assume h,
-- let's call it w with a proof of r w w
cases h with w pf,
-- apply generalization h to w and w to deduce h w w
let contra := h w w,
-- apply resulting implication to proof of r w w
let contra2 := contra pf,
-- that gives a proof of ¬ r w w
-- and that is a ...
contradiction,
-- So the assumption there is such an a is false
-- QED.
end

/- #5
Prove that equality on an inhabited (non-empty) type
is not assymetric.
-/

example (α : Type) (a : α): ¬ is_asymmetric (@eq α) :=
begin
-- assume equality is asymmetric
assume h,

-- expand definition
unfold is_asymmetric at h,

-- apply h to derive a proof of
-- of a = b → a ≠ b
let x := h a a,

-- apply it to a proof of a = a
let y := x (eq.refl a),

-- now we have a contradiction
contradiction,

-- so equality must not be asymmetric
-- if α is an inhabited (non-empty) type
-- which the argument, a, assures is so
end

/- #6
Two natural numbers, p and q, are said to be
"equivalent mod m" if p % m = q % m, which makes
"equivalence mod m" a binary relation on natural
numbers. Here's a formal definition of this binary
relation on the natural numbers (given an m).
-/

def equiv_mod_m (m : ℕ) : ℕ → ℕ → Prop :=
  -- given m, equiv_mod_m is a binary predicate
  λ p q : ℕ, p % m = q % m

/-
Prove using Lean's definitions "equivalence" that
equiv_mod_m is an equivalence relation for any natural
number, m. Here you'll use Lean's definitions of
equivalence, reflexive, symmetric, and transitive.
-/

example : ∀ m : ℕ, equivalence (equiv_mod_m m) :=
begin
-- assume arbitrary "modulus"
intro m,

-- expand definition
unfold equivalence,

-- split goal into three conjuncts
apply (and.intro _ (and.intro _ _)),

-- prove reflexivity
unfold reflexive,
assume x,
unfold equiv_mod_m,

-- prove symmetry
unfold symmetric,
assume x y h,
unfold equiv_mod_m at h,
unfold equiv_mod_m,
rw h,

-- prove transitivity
unfold transitive,
assume a b c,
assume h k,
unfold equiv_mod_m at h,
unfold equiv_mod_m at k,
unfold equiv_mod_m,
rw h,
rw k,
-- That suffices to prove the theorem!
end

/- #7
Consider the relation, tin_rel, that associates people
with U.S. taxpayer id numbers, which we'll represent as
natural numbers here.

Assume this relation is single-valued. Which of the
following properties does this relation have? Give
a very brief justification of each answer. Assume
the domain is all living persons, and the co-domain
is all natural numbers.

-- it's a function: Yes, person has (should have) more than one ssn/tin
-- it's total: No, not every person has an ssn/tin
-- it's injective: yes, no two people can have the same ssn/tin
-- it's surjective (where the co-domain is all ℕ): no, not every ℕ is an ssn
-- it's strictly partial: yes, there are people without ssns
-- it's bijective: no, it's not surjective
-/

/- #8
Suppose r is the empty relation on the natural
numbers. Which of the following properties does
it have? Explain each answer enough to show you
know why your answer is correct.

-- reflexive: no, because not every number is related to itself in r
-- symmetric: yes, because it's a generalization over an empty set
-- transitive: yes, because its a generalization over an empty set
-/

/- #9
Here's a formal definition of this empty relation.
That there are no constructors means there are no
proofs, which is to say that no pair can be proved
to be in this relation, so it must be empty.
-/

inductive empty_rel : ℕ → ℕ → Prop

/-
Formally state and prove you answer for each of the
three properties. That is, for each property assert
that either empty_rel does or does not have it, then
prove your assertion. Include English-language comments
on each line to give the essential elements of a good
English language proof.
-/

example : ¬reflexive empty_rel :=
begin
unfold reflexive,
assume h,
let x := h 0,
cases x,
end

example : symmetric empty_rel :=
begin
unfold symmetric,
assume a b h,
cases h,
end

example : transitive empty_rel :=
begin
assume a b c h k,
cases h,
end

/- #10
A relation, r, is said to have the property of being
a *partial order* if it's reflexive, anti-symmetric,
and transitive. Give a brief English language proof
that the subset relation on the subsets of any set,
S (of objects of some type), is a partial order.

Pf:
Suppose S is a set, with a ⊆ S and b ⊆ S subsets. Then

1. every such a is a subset of itself, so reflexive
2. if a ⊆ b and b ⊆ a then a = b, so anti-symmetric
3. if a ⊆ b and b ⊆ c, then a ⊆ c, so transitive
    given x ∈ a, a ⊆ b means x ∈ b, and similarly
    because x ∈ b then x ∈ c, so x ∈ a → x ∈ c,
    which is the definition of a ⊆ c. QED.
-/

/- #11
Finally we return again to DeMorgan's laws, but
now for sets. You'll recall that these "laws" as we
have seent them so far tell us how to distribute ¬
over ∨ and over ∧. You will also recall that set
intersection (∩) is defined by the conjunction (∧)
of the membership predicates, set union (∪) by the
disjunction (∨), and set complement (Sᶜ for a set,
S), by negation (¬). It makes sense to hypothesize
that we can recast DeMorgan's laws in terms of the
distribution of complement over set union and set
intersection. Indeed we can. Your job is to state
and prove (formally) the first DeMorgan laws for
sets, which states that the complement of a union
of any two sets equals the intersection of their
complements.

Hint: To prove that two sets are equal, S = T, use
the ext tactic. It applies an axiom that states it
suffices to prove S ↔ T, viewing the sets as being
defined by their logical membership predicates. You
know how to handle bi-implications. The rest you
can do by seeing "not," "and," and "or" in place
of complement, conjunction, and disjuction, resp.
-/

example
  (α : Type)
  (a b: set α) :
(a ∪ b)ᶜ = aᶜ ∩ bᶜ :=
begin
-- To prove two sets equal we can show their membership predicates are equivalent
ext,

-- Prove implications in both directions
split,

-- forwards

-- assume premise
assume h,

-- eliminate ∧/∩ in h
-- h says x ∈ a ∨ x ∈ b → false
-- show x ∈ aᶜ and x ∈ bᶜ

apply and.intro,
assume k,
let foo: (x ∈ a ∪ b) :=
  begin left, assumption end,
contradiction,

assume k,
let foo: (x ∈ a ∪ b) :=
  begin right, assumption end,
contradiction,

-- backwards
assume h,
assume k,
cases k,
cases h,
contradiction,
cases h,
contradiction,
end

/-
Post-HW exercise: Write a fluent English language proof
of the preceding proposition. Hint: Model it on the formal
proof.
-/


import tactic --hide

universe u --hide

-- Level name : apply tactic

/-

## The `apply` tactic

If your lemma has the following state:

```
h : P → Q
⊢ Q
```

Here, we assume that `P` implies `Q` and our goal is to prove `Q`. Then the tactic  <mark style ="background-color : #ebdef0 ">`apply h,`</mark>
will change our goal to `P` and leave our assumptions unchanged, i.e. your new state will be

```
h : P → Q
⊢ P
```

The `apply` tactic is useful for *arguing backwards*. It reduces the goal to a potentially easier
goal, without changing any hypotheses.

Here's an example we have already done, but try and do it using the `apply` tactic:

-/

/- Lemma
Let $P,Q$ be logical statements. If $P$ implies $Q$ and $P$ is true, then $Q$ is true.
-/

lemma implications_one_again (P Q : Prop) (h : P → Q) (p : P) : Q :=
begin
  apply h,
  exact p,

end

/- Tactic : apply

## The `apply` tactic

If your lemma looks like

```
h : P → Q
⊢ Q
```

then the tactic `apply h,` will change it to

```
h : P → Q
⊢ P
```

The `apply` tactic is useful for *arguing backwards*. It reduces the goal to a potentially easier
goal, without changing any hypotheses.

-/


section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro np,
  apply np hp,
end

theorem doubleneg_elim : -- "manchado" pela RAA (Redução ao absurdo)
  ¬¬P → P  :=
begin
  intro nnp,
  by_contradiction pboom,
  apply nnp pboom,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    -- parte ¬¬P → P:
    apply doubleneg_elim P,
    -- parte P → ¬¬P:
    apply doubleneg_intro P,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro p_ou_q,
  cases p_ou_q with hp hq,
    -- caso P:
    right,
    exact hp,
    -- caso Q:
    left,
    exact hq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  split,
    -- parte q:
    exact hq,
    -- parte P:
    exact hp,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro np_ou_q,
  intro hp,
  cases np_ou_q with np hq,

    -- caso ¬P:
    have boom : false := np hp,
    exfalso,
    exact boom,

    -- caso Q:
    exact hq,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro p_ou_q,
  intro np,
  cases p_ou_q with hp hq,
    -- caso P:
    contradiction,

    -- caso Q:
    exact hq,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro p_implies_q,
  intro nq,
  intro hp,
  have hq : Q := p_implies_q hp,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro nq_implies_np,
  intro hp,
  by_contradiction nq,
  have np : ¬P := nq_implies_np nq,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
    -- parte (P → Q) → ¬Q → ¬P:
    exact impl_as_contrapositive P Q,

    -- parte (¬Q → ¬P) → P → Q:
    exact impl_as_contrapositive_converse P Q,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro nn_p_ou_np,
  apply nn_p_ou_np,
  right,
  intro hp,
  apply nn_p_ou_np,
  left,
  exact hp,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro hi,
  intro np,
  apply np,
  apply hi,
  intro hp,
  contradiction,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro p_ou_q,
  intro n_np_e_nq,
  cases n_np_e_nq with np nq,
  cases p_ou_q with hp hq,
    -- caso P:
    contradiction,

    -- caso Q:
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  intro np_ou_nq,
  cases np_ou_nq with np nq,
    -- caso ¬P:
    contradiction,

    -- caso ¬Q:
    contradiction,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_p_ou_q,
  split,
    -- parte ¬P:
    intro hp,
    have p_ou_q : (P∨Q),
      left,
      exact hp,
    have boom : false := n_p_ou_q p_ou_q,
    exact boom,

    -- parte ¬Q:
    intro hq,
    have p_ou_q: (P∨Q),
      right,
      exact hq,
    have boom : false := n_p_ou_q p_ou_q,
    exact boom,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro np_e_nq,
  cases np_e_nq with np nq,
  intro p_ou_q,
  cases p_ou_q with hp hq,
    -- caso P:
      have boom : false := np hp,
      exact boom,

    -- caso Q:
      have boom : false := nq hq,
      exact boom,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_p_e_q,
  by_cases hq : Q,
    -- caso Q:
    right,
    intro hp,
    have p_e_q : (P∧Q),
      split,
      -- parte P:
      exact hp,

      -- parte Q:
      exact hq,
    have boom : false := n_p_e_q p_e_q,
    exact boom,

    -- caso ¬Q:
    left,
    exact hq,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro nq_ou_np,
  intro p_e_q,
  cases p_e_q with hp hq, -- uso do ∧
  cases nq_ou_np with nq np, -- uso do ∨
    -- caso ¬Q:
    contradiction,

    -- caso ¬P:
    contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
    -- parte ¬(P ∧ Q) → ¬Q ∨ ¬P:
    exact demorgan_conj P Q,

    -- parte ¬Q ∨ ¬P → ¬(P ∧ Q):
    exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    -- parte ¬(P ∨ Q) → ¬P ∧ ¬Q:
    exact demorgan_disj P Q,

    -- parte ¬P ∧ ¬Q → ¬(P ∨ Q):
    exact demorgan_disj_converse P Q,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro p_e_qour,
  cases p_e_qour with hp q_ou_r, -- Uso da conjunção (Extrair)
  cases q_ou_r with hq hr,
    -- caso Q:
    left,
    split, -- P∧Q:
      -- parte P:
      exact hp,
      -- parte Q:
      exact hq,

    -- caso R:
    right,
    split, -- P∧R:
      -- parte P:
      exact hp,
      -- parte R:
      exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro pEq_ou_pEr,
  cases pEq_ou_pEr with p_e_q p_e_r,
    -- caso P∧Q:
    cases p_e_q with hp hq,
    split,
      -- parte P:
      exact hp,
      -- parte Q∨R:
      left,
      exact hq,

    -- caso P∧R:
    cases p_e_r with hp hr,
    split,
      -- parte P:
      exact hp,
      -- parte Q∨R:
      right,
      exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro p_ou_qEr,
  cases p_ou_qEr with hp q_e_r,
    -- caso P:
    split,
      -- parte P∨Q:
      left,
      exact hp,
      -- parte P∨R:
      left,
      exact hp,

    -- caso Q∧R:
    cases q_e_r with hq hr,
    split,
      -- parte P∨Q:
      right,
      exact hq,
      -- parte P∨R:
      right,
      exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pOUq_e_pOUr,
  cases pOUq_e_pOUr with p_ou_q p_ou_r,
  cases p_ou_q with hp hq,
    -- caso P:
    cases p_ou_r with hp hr,
      -- caso P:
      left,
      exact hp,
      -- caso R:
      left,
      exact hp,
    -- caso Q:
    cases p_ou_r with hp hr,
      -- caso P:
      left,
      exact hp,
      -- caso R:
      right,
      split,
        -- parte Q:
        exact hq,
        -- parte R:
        exact hr,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro pEq_implies_r,
  intro hp,
  intro hq,
  have p_e_q : (P∧Q),
    split,
      -- parte P:
      exact hp,
      -- parte Q:
      exact hq,
  apply pEq_implies_r p_e_q,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro h1,
  intro p_e_q,
  cases p_e_q with hp hq,
  apply h1 hp hq,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro hp,
  exact hp,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro hp,
  left,
  exact hp,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro hq,
  right,
  exact hq,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  exact hp,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  exact hq,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
    -- parte P ∧ P → P:
    intro p_e_p,
    cases p_e_p with hp hp,
    exact hp,

    -- parte P → P ∧ P:
    intro hp,
    split,
      -- parte P:
      exact hp,
      -- parte P:
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    -- parte P ∨ P → P:
    intro p_ou_p,
    cases p_ou_p with hp hp,
      -- caso P:
      exact hp,
      -- caso P:
      exact hp,

    -- parte P → P ∨ P:
    intro hp,
    left,
    exact hp,
end

end propositional

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

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro h1,
  intro x,
  intro px,
  apply h1,
  existsi x,
  exact px,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro h1,
  intro h2,
  cases h2 with x px,
  apply h1 x px,
end

theorem demorgan_forall : -- Manchado (RAA)
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h1,
  by_contradiction h2,
  apply h1,
  intro x,
  by_contradiction npx,
  apply h2,
  existsi x,
  exact npx,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro h1,
  intro h2,
  cases h1 with x npx,
  have px : P x := h2 x,
  apply npx px,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
    -- parte :
    apply demorgan_forall U P,
    -- parte :
    apply demorgan_forall_converse U P,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    -- parte (¬∃ (x : U), P x) → ∀ (x : U), ¬P x:
    apply demorgan_exists U P,
    -- parte (∀ (x : U), ¬P x) → (¬∃ (x : U), P x):
    apply demorgan_exists_converse U P,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro h1,
  intro h2,
  cases h1 with x px,
  apply h2 x px,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro h1,
  intro h2,
  cases h2 with x npx,
  have px : P x := h1 x,
  have boom: false := npx px,
  exact boom,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro h1,
  intro x,
  by_contradiction npx,
  apply h1,
  existsi x,
  exact npx,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro h1,
  by_contradiction h2,
  apply h1,
  intro x,
  intro px,
  apply h2,
  existsi x,
  exact px,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
    -- parte :
    apply forall_as_neg_exists U P,
    -- parte :
    apply forall_as_neg_exists_converse U P,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
    -- parte :
    apply exists_as_neg_forall U P,
    -- parte :
    apply exists_as_neg_forall_converse U P,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro h1,
  cases h1 with x pqx,
  cases pqx with px qx,
  split,
    -- parte (∃x, P x):
    existsi x,
    exact px,
    -- parte (∃x, Q x):
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro h1,
  cases h1 with x pxOUqx,
  cases pxOUqx with px qx,
    -- caso P x:
    left,
    existsi x,
    exact px,
    -- caso Q x:
    right,
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h1,
  cases h1 with epx eqx,
    -- caso ∃x, P x:
    cases epx with x px,
    existsi x,
    left,
    exact px,
    -- caso ∃x, Q x:
    cases eqx with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h1,
  split,
    -- parte ∀x, P x:
    intro x,
    have pxEqx : P x ∧ Q x := h1 x,
    cases pxEqx with px qx,
    exact px,
    -- parte ∀x, Q x:
    intro x,
    have pxEqx : P x ∧ Q x := h1 x,
    cases pxEqx with px qx,
    exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro h1,
  cases h1 with apx aqx,
  intro x,
  split,
    -- parte ∀x, P x:
    have px : P x := apx x,
    exact px,
    -- parte ∀x, Q x:
    have qx : Q x := aqx x,
    exact qx,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro h1,
  cases h1 with apx aqx,
    -- caso ∀x, P x:
    intro x,
    left,
    have px : P x := apx x,
    exact px,
    -- caso ∀x, Q x:
    intro x,
    right,
    have qx : Q x := aqx x,
    exact qx,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


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

import tactic -- imports all the Lean tactics
import data.real.basic -- imports the real numbers

/-!

# The real numbers in Lean

Lean has a copy of of the real numbers. It's called `real`,
but we use the usual notation `ℝ`. Put your cursor on the `ℝ` to find
out how to type it in VS Code.

In this sheet you will prove some basic equalities and inequalities
between "numerical expressions" in Lean. A numeral is something like `37`,
and a numerical expression is something like `(37 + 6) / 4`. To make
things a bit harder, I will throw in some `∃` statements. To make
progress on an `∃` goal, use the `use` tactic.

## Tactics

New tactics you'll need to know about:

* `norm_num` (proves equalities and inequalities involving numerical expressions)
* `use` (if the goal is `∃ x, x + 37 = 42` then `use 8` will change the goal
*        to `8 + 37 = 42`, and `use 10` will change it to `10 + 37 = 42`.

-/

example : (2 : ℝ) + 2 = 4 :=
begin
  sorry
end

example : (2 : ℝ) + 2 ≠ 5 :=
begin
  sorry
end

example : (2 : ℝ) + 2 < 5 :=
begin
  sorry
end

example : ∃ (x : ℝ), 3 * x + 7 = 12 :=
begin
  sorry
end

example : ∃ (x : ℝ), 3 * x + 7 ≠ 12 :=
begin
  sorry
end

example : ∃ (x y : ℝ), 2 * x + 3 * y = 7 ∧ x + 2 * y = 4 :=
begin
  sorry,
end


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
Mostly based on Jeremy Avigad's choose file in lean 2
-/
import data.nat.basic data.nat.prime
import algebra.big_operators

open nat

def choose : ℕ → ℕ → ℕ
| _             0 := 1
| 0       (k + 1) := 0
| (n + 1) (k + 1) := choose n k + choose n (succ k)

@[simp] lemma choose_zero_right (n : ℕ) : choose n 0 = 1 := by cases n; refl

@[simp] lemma choose_zero_succ (k : ℕ) : choose 0 (succ k) = 0 := rfl

lemma choose_succ_succ (n k : ℕ) : choose (succ n) (succ k) = choose n k + choose n (succ k) := rfl

lemma choose_eq_zero_of_lt : ∀ {n k}, n < k → choose n k = 0
| _             0 hk := absurd hk dec_trivial
| 0       (k + 1) hk := choose_zero_succ _
| (n + 1) (k + 1) hk :=
  have hnk : n < k, from lt_of_succ_lt_succ hk,
  have hnk1 : n < k + 1, from lt_of_succ_lt hk,
  by rw [choose_succ_succ, choose_eq_zero_of_lt hnk, choose_eq_zero_of_lt hnk1]

@[simp] lemma choose_self (n : ℕ) : choose n n = 1 :=
by induction n; simp [*, choose, choose_eq_zero_of_lt (lt_succ_self _)]

@[simp] lemma choose_succ_self (n : ℕ) : choose n (succ n) = 0 :=
choose_eq_zero_of_lt (lt_succ_self _)

@[simp] lemma choose_one_right (n : ℕ) : choose n 1 = n :=
by induction n; simp [*, choose]

lemma choose_pos : ∀ {n k}, k ≤ n → 0 < choose n k
| 0             _ hk := by rw [eq_zero_of_le_zero hk]; exact dec_trivial
| (n + 1)       0 hk := by simp; exact dec_trivial
| (n + 1) (k + 1) hk := by rw choose_succ_succ;
    exact add_pos_of_pos_of_nonneg (choose_pos (le_of_succ_le_succ hk)) (nat.zero_le _)

lemma succ_mul_choose_eq : ∀ n k, succ n * choose n k = choose (succ n) (succ k) * succ k
| 0             0 := dec_trivial
| 0       (k + 1) := by simp [choose]
| (n + 1)       0 := by simp
| (n + 1) (k + 1) :=
  by rw [choose_succ_succ (succ n) (succ k), add_mul, ←succ_mul_choose_eq, mul_succ,
  ←succ_mul_choose_eq, add_right_comm, ←mul_add, ←choose_succ_succ, ←succ_mul]

lemma choose_mul_fact_mul_fact : ∀ {n k}, k ≤ n → choose n k * fact k * fact (n - k) = fact n
| 0              _ hk := by simp [eq_zero_of_le_zero hk]
| (n + 1)        0 hk := by simp
| (n + 1) (succ k) hk :=
begin
  cases lt_or_eq_of_le hk with hk₁ hk₁,
  { have h : choose n k * fact (succ k) * fact (n - k) = succ k * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_succ_le_succ hk);
      simp [fact_succ, mul_comm, mul_left_comm],
    have h₁ : fact (n - k) = (n - k) * fact (n - succ k) :=
      by rw [← succ_sub_succ, succ_sub (le_of_lt_succ hk₁), fact_succ],
    have h₂ : choose n (succ k) * fact (succ k) * ((n - k) * fact (n - succ k)) = (n - k) * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_lt_succ hk₁);
      simp [fact_succ, mul_comm, mul_left_comm, mul_assoc],
    have h₃ : k * fact n ≤ n * fact n := mul_le_mul_right _ (le_of_succ_le_succ hk),
  rw [choose_succ_succ, add_mul, add_mul, succ_sub_succ, h, h₁, h₂, ← add_one, add_mul, nat.mul_sub_right_distrib,
      fact_succ, ← nat.add_sub_assoc h₃, add_assoc, ← add_mul, nat.add_sub_cancel_left, add_comm] },
  { simp [hk₁, mul_comm, choose, nat.sub_self] }
end

theorem choose_eq_fact_div_fact {n k : ℕ} (hk : k ≤ n) : choose n k = fact n / (fact k * fact (n - k)) :=
begin
  have : fact n = choose n k * (fact k * fact (n - k)) :=
    by rw ← mul_assoc; exact (choose_mul_fact_mul_fact hk).symm,
  exact (nat.div_eq_of_eq_mul_left (mul_pos (fact_pos _) (fact_pos _)) this).symm
end

theorem fact_mul_fact_dvd_fact {n k : ℕ} (hk : k ≤ n) : fact k * fact (n - k) ∣ fact n :=
by rw [←choose_mul_fact_mul_fact hk, mul_assoc]; exact dvd_mul_left _ _

lemma nat.prime.dvd_choose {p k : ℕ} (hk : 0 < k) (hkp : k < p) (hp : prime p) : p ∣ choose p k :=
have h₁ : p ∣ fact p, from hp.dvd_fact.2 (le_refl _),
have h₂ : ¬p ∣ fact k, from mt hp.dvd_fact.1 (not_le_of_gt hkp),
have h₃ : ¬p ∣ fact (p - k), from mt hp.dvd_fact.1 (not_le_of_gt (nat.sub_lt_self hp.pos hk)),
by rw [← choose_mul_fact_mul_fact (le_of_lt hkp), mul_assoc, hp.dvd_mul, hp.dvd_mul] at h₁;
  exact h₁.resolve_right (not_or_distrib.2 ⟨h₂, h₃⟩)

section binomial
open finset

variables {α : Type*} [comm_semiring α] (x y : α)

/-- The binomial theorem -/
theorem add_pow :
  ∀ n : ℕ, (x + y) ^ n = (range (succ n)).sum (λ m, x ^ m * y ^ (n - m) * choose n m)
| 0        := by simp [range_succ]
| (succ n) :=
have h₁ : x * (x ^ n * y ^ (n - n) * choose n n) =
    x ^ succ n * y ^ (succ n - succ n) * choose (succ n) (succ n),
  by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
have  h₂ : y * (x^0 * y^(n - 0) * choose n 0) = x^0 * y^(succ n - 0) * choose (succ n) 0,
  by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
have h₃ : (range n).sum (λ m, x * (x ^ m * y ^ (n - m) * choose n m) + y *
    (x ^ succ m * y ^ (n - succ m) * choose n (succ m))) =
    (range n).sum (λ m, x ^ succ m * y ^ (succ n - succ m) * ↑(choose (succ n) (succ m))),
  from finset.sum_congr rfl $ λ m hm,
    begin
      simp only [mul_assoc, mul_left_comm y, mul_left_comm (y ^ (n - succ m)), mul_comm y],
      rw [← _root_.pow_succ', add_one, ← succ_sub (mem_range.1 hm)],
      simp [choose_succ_succ, mul_comm, mul_assoc, mul_left_comm, add_mul, mul_add, _root_.pow_succ]
    end,
by rw [_root_.pow_succ, add_pow, add_mul, finset.mul_sum, finset.mul_sum, sum_range_succ, sum_range_succ',
    sum_range_succ, sum_range_succ', add_assoc, ← add_assoc ((range n).sum _),
    ← finset.sum_add_distrib, h₁, h₂, h₃]

end binomial


/-
A predicate is a "proposition with
parameters." For example, a predicate,
fromCharlottesville(p) has a parameter,
p, and asserts that p (for some value
of p) is from Charlottesville.

In Lean, we represent a predicate as
a function that takes one or more more
arguments and reduces to a proposition,
and generally to one that is "about" the
arguments that were given.

So, speaking informally, for example,
fromCharlottesville(Kevin) could be read
as the proposition "Kevin is from Cville".
fromCharlottesville(Mary) is a different
proposition, "Mary is from Cville." A
predicate thus gives rise to a whole
family of propositions, one for each
possible combination of argument
values.

Not every proposition derived by applying
a predicate to one or more arguments will
be true. Rather, the predicate in effect
"picks out" the argument values for which
the corresponding proposition is true, and
thereby identifies them as having a
property of interest, such as the property
of "being from Cville."
-/

/-
Consider a predicate called even(n): that,
for any given natural number value of n,
asserts that n is even. Given a specific
value for n, say 3, this predicate would
reduce to the proposition (even 3), a
proposition that asserts that 3 is even.
Of course 3 isn't even, so (even 3) is
false; but (even 4), on the other hand,
is true, as long as even(n) is properly
defined to express the property of being
even. The key idea is this: the numbers
that have the property are exactly the
numbers for which the corresponding
propositions are true. The predicate
"picks out" the set of numbers with the
given property.
-/

/-
Here's one way to define an evenness
predicate. It uses the existential
quantifier, ∃, which we study in detail
in the next unit. You can read this as
saying  n is even if there exists an m
such that 2 * m = n.
-/
def isEven (n :ℕ) : Prop :=
  ∃ m : nat, 2 * m = n

/-
Note: isEven isn't a proposition, it's
a property!
-/

#check isEven

/-
On the other hand, (isEven 6) is a
proposition, namely the proposition
that there is some value, m, such
that 2 * m = n.
-/

#reduce isEven 6

/-
We can even prove it.
-/

example : isEven 6 :=
begin
unfold isEven,
apply exists.intro 3,
apply rfl,
end

/-
We define a predicate as a function
that takes one or more arguments and
that returns a proposition that, in
general, involves those values.

Consider the following simple example:
a predicate that expresses the property
of a natural number of "being equal to
zero." We write this predicate as a
function that takes a natural number,
n, and returns the proposition that
that number is equal to zero.

Here's such a predicate/function.
-/

def isZero (n : ℕ): Prop := (0 = n)

def nil (n : ℕ) : Prop :=
  false

def allN (n : ℕ) := true

example : allN 8 := true.intro

example : ¬ (nil 8) :=
begin
assume p,
exact false.elim p,
end

/-
First, let's check the type of the
isZero predicate.
-/

#check isZero

/-
The type of isZero is ℕ → Prop. It is
absolutely clear that a predicate is
not a proposition, but is instead a
function that takes one or more values
as arguments (here just one) that that
reduces to a proposition. In general,
such a proposition is "about" those
values. It asserts that they have some
particular property.

Such a proposition need not be true,
and will not be true if the arguments
do not have the property in question.

For example, (isZero 3) is false. On
the other hand, (isZero 0) is true.
-/

example : isZero 0 := rfl

example : ¬(isZero 3) :=
    λ zeq3, nat.no_confusion zeq3

example : ¬(isZero 1) :=
begin
  assume zeq1,
  exact nat.no_confusion zeq1
end

/-
Think carefully about what kind of value
this predicate/function returns when
applied to a particular argument value,
e.g., 3. The result is a proposition,
in particular, a proposition about 3,
namely one that asserts that 0 = 3.

To be clear, isZero is a predicate:
a function from a ℕ-valued argument
to Prop. By contrast, (isZero 3) is
a proposition. Look at the types of
the following expressions.
-/

#check isZero 0 -- a proposition
#check isZero 1 -- a proposition
#check isZero 2 -- a proposition

/-
Now look at the specific propositions
that this predicate/function returns
when applied to particular arguments.
Study this until it's clear to you that
applying a predicate to an argument
returns a proposition about that value.
-/

#reduce isZero 0 -- the proposition 0 = 0
#reduce isZero 1 -- the proposition 0 = 1
#reduce isZero 2 -- the proposition 0 = 2

/-
We can rewrite this function into
an equivalent form in the usual way,
to make the type of the predicate
clear as a function type. The type
of isZero is ℕ → Prop.
-/

def isZero' : ℕ → Prop :=
    λ n, 0 = n

/-
Again, you can think of a predicate
as specifying a *property* of its
argument: in this case the property
of "being equal to zero". Argument
values for which the corresponding
propositions are true have the given
property, and those for which the
corresponding proposition is false,
do not have the property.
-/

-- 0 has the isZero property
theorem zeqz : isZero 0 :=
    eq.refl 0

-- 1 does not have this property
theorem oneeqz : ¬(isZero 1) :=
begin
  unfold isZero,
  assume eq,
  exact nat.no_confusion eq,
end

/-
A predicate basically asserts that
the value given as its argument has
some property. Not every value has a
given property (e.g., 3 does not have
the property of being even). In general
a given predicate is not true for
every value of its argument(s).

EXERCISE: What are other properties
of natural numbers that could be
expressed as predicates?
-/

def isMultOf8: ℕ → Prop :=
  λ n : nat,
    ∃ m : nat, 8 * m = n

def nIsMultOfm: ℕ → ℕ → Prop :=
  λ n m,
    ∃ k, n = k * m

example : isMultOf8 64 :=
begin
unfold isMultOf8,
apply exists.intro 8,
apply rfl,
end

example : nIsMultOfm 64 8 :=
begin
unfold nIsMultOfm,
apply exists.intro 8,
apply rfl,
end

/-

EXERCISE: Define a predicate that is
true for every natural number.
-/

/-
EXERCISE: Define a predicate that is
false for every natural number.
-/

def is_absorbed_by_zero(n: ℕ): Prop := n * 0 = 0

def equals_self_plus_one(n: ℕ): Prop := n = n + 1

/-
EXAMPLE: Thank goodness it's the
weekend.

Below we define a new datatype,
day, with the names of the seven
days of the week as values. Given
this datatype, specify the property
of being a weekend day.
-/

inductive day : Type
| Monday : day
| Tuesday : day
| Wednesday
| Thursday
| Friday
| Saturday
| Sunday

#check day.Tuesday

open day -- no longer need to day. prefix

#check Tuesday

-- Answer

def isWeekend : day → Prop :=
    λ d, d = Saturday ∨ d = Sunday

-- EXERCISE: Show Saturday's a weekend day
theorem satIsWeekend: isWeekend Saturday :=
begin
  unfold isWeekend,   -- unfold tactic
  apply or.intro_left,-- backwards reasoning
  apply rfl  ,         -- finally, equality
end

/-
EXERCISE: Express the property,
of natural numbers, of being a
perfect square. For example, 9
is a perfect square, because 3
is a natural number such that
3 * 3 = 9. By contrast, 12 is
not a perfect square, as there
does not exist a natural number
that squares to 12.

State and prove the proposition
that 9 is a perfect square.
-/

-- Answer

def isASquare: ℕ → Prop :=
    λ n, exists m, n = m ^ 2

theorem isPS9 : isASquare 9 :=
begin
  unfold isASquare,
  exact exists.intro 3 (eq.refl 9)
end

/-
As a final important concept that we just
introduce in this section, we note that we
can think of a predicate as specifying a
set of values: the set of values for which
the predicate (corresponding proposition)
is true.

For example, the predicate, isZero(n),
specifies the set containing only the
natural number, zero. A predicate,
isEven(n), could specify the set of
even natural numbers. Etc. We explore
this idea in more depth in the unit on
sets to come shortly.

EXERCISE: Think of a few more
properties that you could specify
as predicates. What are the domains
of values on which your properties
are defined? What sets do your
predicates define?
-/


/-
© 2021 by the Rector and Visitors of the University of Virginia
-/

/-
In this file we construct a typeclass instance
carrying a proof of the lemma that if K is a
non-empty field then K × K has the structure
of a module. The rationale for this construction
is that (currently) we're representing 1-d affine
space points and vectors as 2-d linear vectors,
in the usual manner.
-/

import algebra.module.basic
import linear_algebra.affine_space.affine_equiv

-- Let K be the carrier set of an arbitrary non-empty field
universes u
variables
  (K : Type u)
  [field K]
  [inhabited K]

--
lemma add_smul_l2 :
  ∀ (r s : K) (x : K × K), (r + s) • x = r • x + s • x :=
begin
  intros,
  ext,
  simp *,
  exact right_distrib r s _,
  simp *,
  exact right_distrib r s _,
end

lemma zero_smul_l2 : ∀ (x : K × K), (0 : K) • x = 0 :=
begin
  intros,
  ext,
  simp *,
  simp *,
end

-- lemma proved by construction of typeclass instance
instance module_K_KxK : module K (K × K) :=
⟨ add_smul_l2 K, zero_smul_l2 K ⟩


import algebra.group

-- BEGIN

variables {G : Type*} [group G]
variables a b c : G

#check one_mul
#check mul_assoc
#check inv_mul_self
#check mul_left_inv
#check mul_right_cancel
#check eq.symm
#check (one_mul b).symm
#check (mul_left_inv a).symm

namespace my_group

theorem mul_right_inv (a : G) : a * a⁻¹ = 1 :=
begin
  have h : a * a⁻¹ * a = 1 * a,
    rw mul_assoc,
    rw mul_left_inv a,
    rw mul_one,
    rw one_mul,
  apply mul_right_cancel h,
end

theorem mul_one (a : G) : a * 1 = a :=
begin
  rw ← mul_left_inv a,
  rw ← mul_assoc a,
  rw mul_right_inv a,
  rw one_mul,
end

theorem mul_inv_rev (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  have h : (a * b)⁻¹ * a * b = b⁻¹ * a⁻¹ * a * b,
    rw mul_assoc b⁻¹ a⁻¹ a,
    rw mul_left_inv a,
    rw mul_one b⁻¹,
    rw mul_left_inv b,
    rw mul_assoc (a * b)⁻¹ a b,
    rw mul_left_inv (a * b),
  rw mul_assoc at h,
  rw mul_assoc at h,
  apply mul_right_cancel h,
end

/- Alternatively, starting with proving mul_cancel_left
and mul_inv_left -/

theorem mul_cancel_left (a b c : G) (h : a * b = a * c) : b = c :=
begin
  rw ← one_mul b,
  rw ← one_mul c,
  rw ← mul_left_inv a,
  repeat {rw mul_assoc},
  rw h,
end

theorem mul_inv_left (a b c : G) (h : b = a⁻¹ * c) : a * b = c :=
begin
  apply mul_cancel_left a⁻¹,
  rw ← mul_assoc,
  rw mul_left_inv,
  rw one_mul,
  rw h,
end

example (a : G) : a * 1 = a :=
begin
  apply mul_inv_left,
  exact (mul_left_inv a).symm, /- it is eqivelant to 'rw mul_left_inv' -/
end

example (a : G) : a * a⁻¹ = 1 :=
begin
  apply mul_inv_left,
  rw mul_one a⁻¹, /- 'exact (mul_one a⁻¹).symm' works too -/
end

example (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_cancel_left (a * b),
  rw mul_right_inv,
  rw mul_assoc,
  rw ← mul_assoc b b⁻¹,
  rw mul_right_inv,
  rw one_mul,
  rw mul_right_inv,
end

end my_group

#check mul_right_inv
#check mul_one
#check mul_inv_rev

-- END


import Mathlib.Tactic.NormNum
import Mathlib.Tactic.Linarith
import Mathlib.Data.Nat.Prime
variable (P Q  : Prop)
variable (α β : Type)
variable (R S: ℕ → Prop)
open Function Nat

/-
# Quantifiers

# Existential ∃ tactics: use / cases

If our goal is `⊢ ∃(x:A), P` (where P may dep on x) and we know that `a : A` is a term
with the required property, then we can tell Lean to `use a` and our goal will change to `⊢ P`.
In fact if we can prove `P` using `refl` then Lean will do this automatically.

If we have `h : ∃(x:A), P` in the local context then  `cases h with x hx`
will give us `x : A` and `hx : P` in our local context.

[Notice how similar this is to how we used `cases h with hp hq` with `h : P ∧ Q`]

# Universal ∀ tactics: intro, apply, specialize

If we have a goal `⊢ ∀(n:ℕ), P n` then we can use `intro n` and the our goal will
change to `⊢ P n`. (For Lean a `∀` statement is basically a function.)

If we have `(h : ∀(x:A), P x)` and we want to use this hypotesis for a particular choice
of `x` then we can use `specialize h x` or (if goal is `⊢ P x`) just `apply h`

# New tactics for numerical/arithmetic proofs: norm_num and linarith

`norm_num` can prove results involving actual numerical expressions such as `4*3^2 < 50`

`linarith` can prove results involving linear arithmetic including inequalities.

# New tactics to help us make sense of definitions: unfold and dsimp

`unfold blah` will unfold a definition of `blah`in the goal.
Use unfold when you're not sure what a definition means.

`dsimp` will simplify the goal using definitions.
Use dsimp if the goal looks unreadable.  -/

-- 01
example : ∃(n:ℕ), n = 20 :=by
  use 20

-- 02
example : ∃(n:ℕ), 200 < n:=by
  use 201
  norm_num

-- 03
example : ∃(P:Prop), P:=by
use True
trivial

-- 04
example : ∃ (P:Prop), ¬ P → P:=by
use True
intro _
trivial

-- 05
example (h: ∃(n:ℕ), n.Prime ∧ 10 < n) : ∃(k:ℕ), k.Prime ∧ 7 < k :=by
obtain ⟨n,hn1,hn2⟩:= h
use n
apply And.intro hn1
linarith

-- 06 Hint : `Even n` is defined to be `∃(r:ℕ), n = r + r`
example (n : ℕ) : Even (4 * n):=by
  rw  [Even]
  use (2*n)
  rw [← two_mul,←mul_assoc ]
  rfl

-- 07
example (h: ∃(n:ℕ), Even n ∧ Even (n+1)) : ∃(k:ℕ), Even (2*k + 1) :=by
rcases h with ⟨n,hn1,hn2⟩
use n
rw [Even] at *
rcases hn1 with ⟨r,hr⟩
rcases hn2 with ⟨s,hs⟩
use (r+s)
rw [two_mul, ← add_assoc, add_comm, add_assoc,add_assoc, add_comm s,←add_assoc,←add_assoc,←add_assoc]
rw[← hr,add_assoc,add_assoc,← hs,add_comm,add_assoc]

-- 08
example : ∀ (n:ℕ), ∃ (k:ℕ), n < k:=by
intro n
use n.succ
exact lt_succ_self n

-- 09
example : ∀(n:ℕ), R n ∧ S n → ∃(k:ℕ), S k:=by
intro n hn
exact ⟨n,hn.2⟩

-- 10
example (f: ℕ → ℕ) (k : ℕ): Surjective f → ∃(n:ℕ), f n = 2023^k := by
intro hf
apply hf

-- 11 Hint: if we have `h : ∀ (n : ℕ), f n = g n` then `rw (h n)` will replace `f n` by `g n` in the goal.
example (f g: ℕ → ℕ) (h : ∀ n, f n = g n):  Injective f → Injective g:=by
intro hf
intro a b hab
apply hf
rwa [h a,h b]

-- 12
example (f : ℕ → ℕ) (m n : ℕ): Bijective f → f m = f n → m = n:=by
intro hf heq
apply hf.1
exact heq

/-
If (a b: ℕ) then `a ∣ b` means `a divides b`, ie. `∃(c:ℕ), b = a*c`
Note that this symbol is `\|` not `|` which is obviously not the same...-/

-- 13
example (h : ∀n:ℕ, 3 ≤ n → 2 ∣ n → ¬n.Prime ) :∀(k:ℕ), Even k → 3 ≤ k → ¬ k.Prime :=by
  intro k hk1 hk2
  apply h
  exact hk2
  cases' hk1 with r hr
  use r
  rwa [two_mul]

-- We can use `⟨n,h⟩` notation for `∃ n, h`
-- 14
example (n : ℕ)(h : n.Prime) : ∃ (k : ℕ), k.Prime:=by
  exact ⟨n,h⟩

-- 15 what if we don't have the hypothesis `n.Prime`
example  : ∃ (k : ℕ), k.Prime:=by
  refine' ⟨2,Nat.prime_two⟩

-- 16 To complete the next proof we need to show 2027 is Prime. Luckily `norm_num` will do it.
example  : ∃ (k : ℕ), 2026 < k ∧ k.Prime:=by
  refine'  ⟨2027,by norm_num,_⟩
  --norm_num -- the primality proving part of norm_num does not seem to exist in Lean4
  norm_num
  sorry

-- /-
-- # New tactic: push_neg

-- Given a proposition with quantifiers involving a negation  `push_neg` will push the negation inside
-- the expression as far as possible

-- Like `rw` we can also use it to simplify terms in the local context eg `push_neg at h`  -/

-- 17
example : ¬ ∃ (n:ℕ), ∀ (k:ℕ), k < n:=by
  push_neg
  intro n
  use n

-- 18 Hint: If m,n ∈ ℕ then `max m n` is their maximum, `norm_num` may be useful again here
example : ∀ (m: ℕ), ∀ (n: ℕ), ∃ (k:ℕ), m ≤ k ∧ n ≤ k:=by
intro m n
refine' ⟨m+n,_,_⟩
apply Nat.le_add_right m
apply Nat.le_add_left n

variable (pr : α → Prop)
/- A function from a type to Prop is usually called a `predicate`
   For example `nat.Prime: ℕ → Prop` -/

-- 19
example : (∀ (a:α), pr a) ↔ ¬ ∃ (b:α), ¬ pr b:=by
apply Iff.intro
· intro h
  push_neg
  exact h
· intro h
  push_neg at h
  exact h

/-
In the previous example we had a `split` and the two parts used almost the same
code. One way to do such proofs is to use the `try {code}` tactic, which tries the
`code` but doesn't complain if it fails.
-/
-- 20
-- example : (∀ (a:α), pr a) ↔ ¬ ∃ b:α, ¬ pr b:=by
-- apply Iff.intro
-- repeat
--   intro h
--   try {push_neg}  -- this succeeds the 1st time but silently fails the 2nd time
--   try {push_neg at h}  --this succeeds the 2nd time but silently fails the 1st time
--   exact h

-- 21
example : ∃ (n: ℕ), n + 2 = 5 ∧ 2 * n = 6:=by
use 3
norm_num

-- 22 beware the divides `∣` symbol is not `|` but `\|`
example : ¬(∀ n: ℕ, n.Prime ∨ (2 ∣ n)):=by
intro hf
contrapose hf
push_neg
use 9
norm_num
intro h2

sorry

-- -- 23 Hint: Even n := ∃(r:ℕ), n = r + r
-- example : ∀ (n m:ℕ), Even n ∧ Even m → Even (n+m):=
-- by
--  admit

-- -- A relation r on ℕ
-- variable (r : ℕ → ℕ → Prop)
-- -- 24
-- example:  reflexive r → r 17 17:=
-- by
--   unfold reflexive,
--   admit

-- -- 25
-- example : symmetric r →  r 13 2 → r 2 13:=
-- by
--   unfold symmetric,
--   admit

-- -- 26
-- example (a b c : ℕ) : equivalence r → r a b →  r b c →  r a c:=
-- by
--   unfold equivalence transitive,
--   admit

-- -- 27
-- example (a b c : ℕ) : equivalence r → r a b →  r b c →  r c a:=
-- by
--   admit

-- -- 28
-- example (h1 : symmetric r) (h2: ∃ (a b:ℕ), r a b ∧ a ≠ b) :
-- (∀ {m n: ℕ}, r m n → r n m → m = n) → false :=
-- by
--   admit

-- -- 29 Hint: dsimp may be useful here (you'll know when to use it!)
-- example : ∃! (n:ℕ), ∀ (k:ℕ), n ≤ k :=
-- by
--   admit

-- -- 30
-- example : ¬∃ (n:ℕ), ∀ (k:ℕ), k ≤ n :=
-- by
--   admit

-- /- If we want to define a function and then use it we use the `def` keyword

--  We can define the relation `parity` on the integers ℤ
--  `parity a b` holds iff `a + b` is Even -/
-- def parity : ℤ → ℤ → Prop :=
-- by
--   intros a b,
--   exact Even (a+b),

-- -- Lets prove this is an equivalence relation
-- -- 31
-- example : equivalence parity :=
-- by
--   split,
--   {
--     admit
--   },
--   split,
--   {
--     admit
--   },
--   {
--     admit
--   },

-- -- The relation `r a b` iff `a = b mod m` iff `m ∣ (b-a)`
-- def mod (m: ℤ) : ℤ → ℤ → Prop :=
-- by
--   intros a b,
--   exact m ∣ (b-a),

-- -- 32
-- example :∀ (m:ℤ), equivalence (mod m) :=
-- by
--   admit


import Mathlib.Data.Nat.Basic
import Notes4.IndTypes

/-
One of the features that sets Lean apart from other programming languages, and
motivates this course, is the ability to reason your written code in a
computational way.

We can state theorems expressing invariants of code and then write more
code to produce a proof verifying that theorem.

For example, we expect, implemented correctly, taking lenghts of
lists is additive under appending an element, ie length a::l = 1 + length l.
-/

theorem list_length_add_of_cons {α : Type} (a : α) (l: List α) :
    (a::l).length = 1 + l.length := sorry

/-
Theorems are terms of type Prop. Prop is a special type in Lean whose terms
are propositions in the sense of mathematical logic. For example,

- There are infinitely many prime numbers.
- If f and g are differentiable, then so is f + g.
- There is at most one path between any two vertices in a tree.
- All natural numbers are odd.

Propositions can have proofs or not. These proofs are built by stringing
together existing proofs to make new ones using basic rules of inference.
Thanks to this computational nature, one can model propositional (and
higher) logic in Lean and other languages like Coq, Agda, HOL, Isabelle,
F-star, Idris,...

This provides the ability to use the _same tools_ to create proofs as
to program. Assuming the underlying implementation sound, then proving a
theorem universally assures that correctness of the code, with regards
to that particular statement. For list_length_add_of_append, giving a proof
assures that our implementation of append is _always_ additive with
respect to lengths of lists. There are no edge cases and no bugs.

What does a proof look like?
-/

example {α : Type} (a : α) (l: List α) : (a::l).length = 1 + l.length := by
  match l with
  | [] => simp
  | a'::as =>
    simp only [List.length]
    ac_rfl

/-
_You do not have to have any understanding of the actual tactics in
use in these examples right now._
Most of the course will devoted to obtaining facility
in constructing proofs using these tactics.

Right now, you should familiarize yourself with the structure of a proof.

Let's step through this proof to get a sense of the pieces involved. Every
proof starts with either an `example` or `theorem` keyword. If we use the
theorem keyword, we need to provide a unique name. Otherwise, we do not.

Then comes the hypotheses of the statement
`{α : Type} (a : α) (l: List α)` which say we have
- some type α which Lean should try to fill in from the context,
- a term `a` of type `α`, and
- a list `l` with elements of type `α`.

At the end of the hypotheses, we find a `:` to indicate the type of
proposition will follow. Here the type is `(a::l).length = 1 + l.length`.
This is the proposition we are trying to prove.

Next, we have `:=` which tells Lean that the statement of the type is done
and next we will provide the term of that term, here a proof.

Immediately after `:=` we have a `by` which enters _tactic mode_. We
will start out using proving tactic mode to construct proofs.

Tactics are a method of automation to construct terms of desired types.
As such, they fall in the realm of _meta_-programming. A well-written
tactic can be incredibly useful and great time-saver.

The remainder of the proof consists of calls to appropriate tactics:
- `match` allows to reduce to providing proofs for each constructor
of List, nil and cons.
- In the nil, [], branch, we call on Lean's simplifier _simp_ which is
a extensiable rewriting system.
- In the cons branch, we again called simp but using the definition of
List.length to simplify the goal. Then we called the `ac_rfl` tactic
which closes goals using associativity, commutativity, and reflexivity.

In general, there is a zoo of tactics of which we will see only a corner.
If time allows, we will discuss how to write tactics.

Let's see another example of the structure of a theorem and its proof.
-/

theorem exp_eq_exp' (b e : Nat) : exp b e = exp' b e := by
  match e with
  | 0 => simp [exp,exp']
  | n+1 =>
    simp only [exp,exp']
    simp only [Nat.add]
    rw [exp_eq_exp' b n]

/-
Now we know from `exp_eq_exp'` that our two definition of natural
number exponentiation always produce the same outputs.

Here we saw another tactic `rw` which is also a rewriting tactic.

Now that we know that both version of exp are equal let's prove a
basic property of exponention.
-/

example : ∀ a b c : Nat, exp a (b + c) = (exp a b)*(exp a c) := by
  intro a b c
  induction' b with n ih
  · simp [exp]
  · rw [Nat.succ_eq_add_one]
    rw [Nat.add_assoc,Nat.add_comm 1 c,←Nat.add_assoc]
    simp [exp]
    rw [Nat.mul_assoc,Nat.mul_comm a _,←Nat.mul_assoc]
    rw [ih]


import tactic

/-

# For which positive integers n does n+1 divide n^2+1?

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

Here's the trick: n+1 divides n^2-1 so if it divides n^2+1
then it divides (n^2+1)-(n^2-1)=2, and n+1>=2 so n+1=2 giving
n=1 as the only solution.

-/

#eval (2 : ℕ) - 3
#eval (2 : ℤ) - 3

--set_option pp.notation false

example (n : ℕ) (hn : 0 < n) : (n + 1) ∣ (n^2 + 1) ↔ n = 1 :=
begin
  split,
  { rintro ⟨c, hc⟩,
    have h1 : (n : ℤ)^2 - 1 = (n + 1) * (n - 1) := by ring,
    zify at hc,
    have h2 : ((n : ℤ) + 1) ∣ 2,
    { use c - (n - 1),
      rw [mul_sub, ← hc, ← h1],
      ring },
    have h3 : (n : ℤ) + 1 ≤ 2 := int.le_of_dvd zero_lt_two h2,
    linarith },
  { rintro rfl,
    norm_num }
end


-- Suma_de_potencias_de_dos.lean
-- Suma de potencias de dos
-- José A. Alonso Jiménez
-- Sevilla, 25 de septiembre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que
--    1 + 2 + 2² + 2³ + ... + 2⁽ⁿ⁻¹⁾ = 2ⁿ - 1
-- ---------------------------------------------------------------------

import algebra.big_operators
import tactic
open finset nat

open_locale big_operators
set_option pp.structure_projections false

variable (n : ℕ)

-- 1ª demostración
example :
  ∑ k in range n, 2^k = 2^n - 1 :=
begin
  induction n with n HI,
  { simp, },
  { calc ∑ k in range (succ n), 2^k
         = ∑ k in range n, 2^k + 2^n
             : sum_range_succ (λ x, 2 ^ x) n
     ... = (2^n - 1) + 2^n
             : congr_arg2 (+) HI rfl
     ... = (2^n + 2^n) - 1
             : by omega
     ... = 2^n * 2 - 1
             : by {congr; simp}
     ... = 2^(succ n) - 1
             : by {congr' 1; ring_nf}, },
end


import algebra.big_operators data.real.cau_seq tactic.ring algebra.archimedean data.nat.choose analysis.limits

open nat is_absolute_value

variables {α : Type*} {β : Type*} (f g : ℕ → α) (n m : ℕ)

local infix `^` := monoid.pow
local notation f ` ∑ ` : 90 n : 90  := finset.sum (finset.range n) f

section sum_range
variable [add_comm_monoid α]

lemma sum_range_succ : f ∑ succ n = f n + f ∑ n :=
have h : n ∉ finset.range n := by rw finset.mem_range; exact lt_irrefl _,
by rw [finset.range_succ, finset.sum_insert h]

lemma sum_range_succ' : ∀ n : ℕ, f ∑ succ n = (λ m, f (succ m)) ∑ n + f 0
| 0        := by simp
| (succ n) := by rw [sum_range_succ (λ m, f (succ m)), add_assoc, ← sum_range_succ'];
                 exact sum_range_succ _ _

lemma sum_range_comm : f ∑ n = (λ m, f (n - (succ m))) ∑ n :=
begin
  induction n with n hi,
  { simp },
  { rw [sum_range_succ, sum_range_succ', hi, succ_sub_one, add_comm],
    simp [succ_sub_succ] }
end

end sum_range

theorem binomial [comm_semiring α] (x y : α) : ∀ n : ℕ,
    (x + y)^n = (λ m, x^m * y^(n - m) * choose n m) ∑ succ n
| 0        := by simp
| (succ n) :=
begin
  rw [_root_.pow_succ, binomial, add_mul, finset.mul_sum, finset.mul_sum, sum_range_succ, sum_range_succ',
      sum_range_succ, sum_range_succ', add_assoc, ← add_assoc (_ ∑ n), ← finset.sum_add_distrib],
  have h₁ : x * (x^n * y^(n - n) * choose n n) = x^succ n * y^(succ n - succ n)
      * choose (succ n) (succ n) :=
    by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
  have  h₂ : y * (x^0 * y^(n - 0) * choose n 0) = x^0 * y^(succ n - 0) * choose (succ n) 0 :=
    by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
  have h₃ : (λ m, x * (x^m * y^(n - m) * choose n m) + y * (x^succ m * y^(n - succ m)
      * choose n (succ m))) ∑ n
      = (λ m, x^succ m * y^(succ n - succ m) * ↑(choose (succ n) (succ m))) ∑ n :=
    finset.sum_congr rfl
      begin
        assume m hm,
        rw finset.mem_range at hm,
        rw [← mul_assoc y, ← mul_assoc y, mul_right_comm y, ← _root_.pow_succ, add_one, ← succ_sub hm],
        simp [succ_sub_succ, _root_.pow_succ, choose_succ_succ, mul_add, mul_comm,
            mul_assoc, mul_left_comm]
      end,
  rw [h₁, h₂, h₃]
end

#check binomial


section
variable (α : Type) (p q : α → Prop) (r : α → α → Prop)

/- UNIVERSAL QUANTIFICATION -/

-- We can leave off `: α` if Lean can infer it (here via `p`/`q`)
example : (∀ x, p x) → (∀ x, p x → q x) → (∀ x, q x) :=
  fun hp hpq x => hpq x (hp x)

-- The reverse direction of the slides example
example : (∀ x, p x ∧ q x) → (∀ x, p x) ∧ (∀ x, q x) :=
  -- for the `⟨...⟩` and `.left/right` notations, see Solutions/Exercise1.lean
  fun hpq => ⟨fun x => And.left (hpq x),
              fun x => (hpq x).right⟩

-- TODO: Prove the corresponding forward/reverse lemma(s) for `∨` (that hold)!
-- hint: input `∀` as `\all`
example : (∀ x, p x) ∨ (∀ x, q x) → (∀ x, p x ∨ q x) :=
  -- We can match multiple terms separated by commas in parallel
  -- Here `fun | ...` is short for `fun h x => match h, x with | ...`
  fun | Or.inl hp, x => Or.inl (hp x)
      | Or.inr hq, x => Or.inr (hq x)

-- We can bind multiple variables in a single `∀`
example : (∀ x y, r x y) → (∀ y x, r x y) :=
  fun h y x => h x y

/- EXISTENTIAL QUANTIFICATION -/

/-
Interestingly, in contrast to the universal quantifier, the existential quantifier is not primitive
but can be specified as an inductive type:
```
inductive Exists (p : α → Prop) : Prop where
  | intro (w : α) (h : p w) : Exists p
```
That is, `Exists.intro` takes/offers a "witness" and a proof that the predicate holds for the witness.
Instead of `Exists (fun x => p x)`, we can also write `∃ x, p x` (input `∃` as `\ex`).
-/

example : (∃ x, p x ∧ q x) → (∃ x, p x) ∧ (∃ x, q x) :=
  fun | Exists.intro x hpq => ⟨Exists.intro x hpq.left, Exists.intro x hpq.right⟩

example : ¬(∃ x, p x) → (∀ x, ¬ p x) :=
  fun h x hp => h ⟨x, hp⟩

example : (∀ x, ¬ p x) → ¬(∃ x, p x) :=
  fun hnotp ⟨x, hpx⟩ => hnotp x hpx

example : (∃ x, ¬ p x) → ¬ (∀ x, p x) :=
  fun ⟨x, hnotpx⟩ hp => hnotpx (hp x)

section
open Classical
-- The following example can only be solved using the classical axioms
-- This one is pretty tricky again, don't feel bad about skipping it
-- hint: use the following helper theorem that can be derived from `em`:
#check byContradiction
-- hint: you may even need to use it more than once
example : ¬(∀ x, p x) → (∃ x, ¬ p x) :=
  fun hnotp => byContradiction (fun h => hnotp (fun x => byContradiction (fun hpx => h ⟨x, hpx⟩)))
end

-- TODO: Decide for yourself what variables you need to model and prove the following
-- important real-world observation, which is sometimes called "drinker paradox":
--   "If there is at least one person in the pub, then there is someone in the pub such that,
--    if (s)he is drinking, then everyone in the pub is drinking."
-- hint: you can define "is in pub" either as a predicate variable on a "Person" type (`(Person : Type)`),
--   or, more simply, directly as a type "Occupant" since we are not interested in persons outside the pub
-- hint: you might need classical logic again
section Drinker

open Classical
-- NOTE: instead of `(∃ p : Occupant, True)`, the equivalent simple `Occupant` could also be used
example (Occupant : Type) (drinking : Occupant → Prop) : (∃ p : Occupant, True) →
    ∃ p, drinking p → ∀ p', drinking p' :=
  fun ⟨p, _⟩ => match em (∃ p', ¬ drinking p') with
    | Or.inl ⟨p', hp'nodrink⟩ => ⟨p', fun hp'drink => False.elim (hp'nodrink hp'drink)⟩
    | Or.inr h => ⟨p, fun hpdrink p' => byContradiction (fun hp'nodrink => h ⟨p', hp'nodrink⟩)⟩

end Drinker

/- EQUALITY -/

example : ∀ a b c : α, a = b → b = c → a = c :=
  fun a b c hab hbc => hab ▸ hbc

example : ∀ a : α, ∃ b : α, b = a :=
  fun a => ⟨a, rfl⟩

-- "`Eq` is the least reflexive relation"
example : (∀ a, r a a) → (∀ a b, a = b → r a b) :=
  fun hrrefl a b hab =>
    hab ▸ hrrefl a

end


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Chris Hughes, Tim Baanen
-/
import data.matrix.pequiv
import data.matrix.block
import data.fintype.card
import group_theory.perm.fin
import group_theory.perm.sign
import algebra.algebra.basic
import tactic.ring
import linear_algebra.alternating
import linear_algebra.pi

/-!
# Determinant of a matrix

This file defines the determinant of a matrix, `matrix.det`, and its essential properties.

## Main definitions

 - `matrix.det`: the determinant of a square matrix, as a sum over permutations
 - `matrix.det_row_alternating`: the determinant, as an `alternating_map` in the rows of the matrix

## Main results

 - `det_mul`: the determinant of `A ⬝ B` is the product of determinants
 - `det_zero_of_row_eq`: the determinant is zero if there is a repeated row
 - `det_block_diagonal`: the determinant of a block diagonal matrix is a product
   of the blocks' determinants

## Implementation notes

It is possible to configure `simp` to compute determinants. See the file
`test/matrix.lean` for some examples.

-/

universes u v w z
open equiv equiv.perm finset function

namespace matrix
open_locale matrix big_operators

variables {m n : Type*} [decidable_eq n] [fintype n] [decidable_eq m] [fintype m]
variables {R : Type v} [comm_ring R]

local notation `ε` σ:max := ((sign σ : ℤ ) : R)

/-- `det` is an `alternating_map` in the rows of the matrix. -/
def det_row_alternating : alternating_map R (n → R) R n :=
((multilinear_map.mk_pi_algebra R n R).comp_linear_map (linear_map.proj)).alternatization

/-- The determinant of a matrix given by the Leibniz formula. -/
abbreviation det (M : matrix n n R) : R :=
det_row_alternating M

lemma det_apply (M : matrix n n R) :
  M.det = ∑ σ : perm n, σ.sign • ∏ i, M (σ i) i :=
multilinear_map.alternatization_apply _ M

-- This is what the old definition was. We use it to avoid having to change the old proofs below
lemma det_apply' (M : matrix n n R) :
  M.det = ∑ σ : perm n, ε σ * ∏ i, M (σ i) i :=
by simp [det_apply, units.smul_def]

@[simp] lemma det_diagonal {d : n → R} : det (diagonal d) = ∏ i, d i :=
begin
  rw det_apply',
  refine (finset.sum_eq_single 1 _ _).trans _,
  { intros σ h1 h2,
    cases not_forall.1 (mt equiv.ext h2) with x h3,
    convert mul_zero _,
    apply finset.prod_eq_zero,
    { change x ∈ _, simp },
    exact if_neg h3 },
  { simp },
  { simp }
end

@[simp] lemma det_zero (h : nonempty n) : det (0 : matrix n n R) = 0 :=
(det_row_alternating : alternating_map R (n → R) R n).map_zero

@[simp] lemma det_one : det (1 : matrix n n R) = 1 :=
by rw [← diagonal_one]; simp [-diagonal_one]

lemma det_is_empty [is_empty n] {A : matrix n n R} : det A = 1 :=
by simp [det_apply]

@[simp] lemma coe_det_is_empty [is_empty n] : (det : matrix n n R → R) = function.const _ 1 :=
by { ext, exact det_is_empty, }

lemma det_eq_one_of_card_eq_zero {A : matrix n n R} (h : fintype.card n = 0) : det A = 1 :=
begin
  haveI : is_empty n := fintype.card_eq_zero_iff.mp h,
  exact det_is_empty,
end

/-- If `n` has only one element, the determinant of an `n` by `n` matrix is just that element.
Although `unique` implies `decidable_eq` and `fintype`, the instances might
not be syntactically equal. Thus, we need to fill in the args explicitly. -/
@[simp]
lemma det_unique {n : Type*} [unique n] [decidable_eq n] [fintype n] (A : matrix n n R) :
  det A = A (default n) (default n) :=
by simp [det_apply, univ_unique]

lemma det_eq_elem_of_subsingleton [subsingleton n] (A : matrix n n R) (k : n) :
  det A = A k k :=
begin
  convert det_unique _,
  exact unique_of_subsingleton k
end

lemma det_eq_elem_of_card_eq_one {A : matrix n n R} (h : fintype.card n = 1) (k : n) :
  det A = A k k :=
begin
  haveI : subsingleton n := fintype.card_le_one_iff_subsingleton.mp h.le,
  exact det_eq_elem_of_subsingleton _ _
end

lemma det_mul_aux {M N : matrix n n R} {p : n → n} (H : ¬bijective p) :
  ∑ σ : perm n, (ε σ) * ∏ x, (M (σ x) (p x) * N (p x) x) = 0 :=
begin
  obtain ⟨i, j, hpij, hij⟩ : ∃ i j, p i = p j ∧ i ≠ j,
  { rw [← fintype.injective_iff_bijective, injective] at H,
    push_neg at H,
    exact H },
  exact sum_involution
    (λ σ _, σ * swap i j)
    (λ σ _,
      have ∏ x, M (σ x) (p x) = ∏ x, M ((σ * swap i j) x) (p x),
        from fintype.prod_equiv (swap i j) _ _ (by simp [apply_swap_eq_self hpij]),
      by simp [this, sign_swap hij, prod_mul_distrib])
    (λ σ _ _, (not_congr mul_swap_eq_iff).mpr hij)
    (λ _ _, mem_univ _)
    (λ σ _, mul_swap_involutive i j σ)
end

@[simp] lemma det_mul (M N : matrix n n R) : det (M ⬝ N) = det M * det N :=
calc det (M ⬝ N) = ∑ p : n → n, ∑ σ : perm n, ε σ * ∏ i, (M (σ i) (p i) * N (p i) i) :
  by simp only [det_apply', mul_apply, prod_univ_sum, mul_sum,
    fintype.pi_finset_univ]; rw [finset.sum_comm]
... = ∑ p in (@univ (n → n) _).filter bijective, ∑ σ : perm n,
    ε σ * ∏ i, (M (σ i) (p i) * N (p i) i) :
  eq.symm $ sum_subset (filter_subset _ _)
    (λ f _ hbij, det_mul_aux $ by simpa only [true_and, mem_filter, mem_univ] using hbij)
... = ∑ τ : perm n, ∑ σ : perm n, ε σ * ∏ i, (M (σ i) (τ i) * N (τ i) i) :
  sum_bij (λ p h, equiv.of_bijective p (mem_filter.1 h).2) (λ _ _, mem_univ _)
    (λ _ _, rfl) (λ _ _ _ _ h, by injection h)
    (λ b _, ⟨b, mem_filter.2 ⟨mem_univ _, b.bijective⟩, coe_fn_injective rfl⟩)
... = ∑ σ : perm n, ∑ τ : perm n, (∏ i, N (σ i) i) * ε τ * (∏ j, M (τ j) (σ j)) :
  by simp only [mul_comm, mul_left_comm, prod_mul_distrib, mul_assoc]
... = ∑ σ : perm n, ∑ τ : perm n, (((∏ i, N (σ i) i) * (ε σ * ε τ)) * ∏ i, M (τ i) i) :
  sum_congr rfl (λ σ _, fintype.sum_equiv (equiv.mul_right σ⁻¹) _ _
    (λ τ,
      have ∏ j, M (τ j) (σ j) = ∏ j, M ((τ * σ⁻¹) j) j,
        by { rw ← σ⁻¹.prod_comp, simp only [equiv.perm.coe_mul, apply_inv_self] },
      have h : ε σ * ε (τ * σ⁻¹) = ε τ :=
        calc ε σ * ε (τ * σ⁻¹) = ε ((τ * σ⁻¹) * σ) :
          by { rw [mul_comm, sign_mul (τ * σ⁻¹)], simp only [int.cast_mul, units.coe_mul] }
        ... = ε τ : by simp only [inv_mul_cancel_right],
      by { simp_rw [equiv.coe_mul_right, h], simp only [this] }))
... = det M * det N : by simp only [det_apply', finset.mul_sum, mul_comm, mul_left_comm]

/-- The determinant of a matrix, as a monoid homomorphism. -/
def det_monoid_hom : matrix n n R →* R :=
{ to_fun := det,
  map_one' := det_one,
  map_mul' := det_mul }

@[simp] lemma coe_det_monoid_hom : (det_monoid_hom : matrix n n R → R) = det := rfl

/-- On square matrices, `mul_comm` applies under `det`. -/
lemma det_mul_comm (M N : matrix m m R) : det (M ⬝ N) = det (N ⬝ M) :=
by rw [det_mul, det_mul, mul_comm]

/-- On square matrices, `mul_left_comm` applies under `det`. -/
lemma det_mul_left_comm (M N P : matrix m m R) : det (M ⬝ (N ⬝ P)) = det (N ⬝ (M ⬝ P)) :=
by rw [←matrix.mul_assoc, ←matrix.mul_assoc, det_mul, det_mul_comm M N, ←det_mul]

/-- On square matrices, `mul_right_comm` applies under `det`. -/
lemma det_mul_right_comm (M N P : matrix m m R) :
  det (M ⬝ N ⬝ P) = det (M ⬝ P ⬝ N) :=
by rw [matrix.mul_assoc, matrix.mul_assoc, det_mul, det_mul_comm N P, ←det_mul]

lemma det_units_conj (M : units (matrix m m R)) (N : matrix m m R) :
  det (↑M ⬝ N ⬝ ↑M⁻¹ : matrix m m R) = det N :=
by rw [det_mul_right_comm, ←mul_eq_mul, ←mul_eq_mul, units.mul_inv, one_mul]

lemma det_units_conj' (M : units (matrix m m R)) (N : matrix m m R) :
  det (↑M⁻¹ ⬝ N ⬝ ↑M : matrix m m R) = det N := det_units_conj M⁻¹ N

/-- Transposing a matrix preserves the determinant. -/
@[simp] lemma det_transpose (M : matrix n n R) : Mᵀ.det = M.det :=
begin
  rw [det_apply', det_apply'],
  refine fintype.sum_bijective _ inv_involutive.bijective _ _ _,
  intros σ,
  rw sign_inv,
  congr' 1,
  apply fintype.prod_equiv σ,
  intros,
  simp
end

/-- Permuting the columns changes the sign of the determinant. -/
lemma det_permute (σ : perm n) (M : matrix n n R) : matrix.det (λ i, M (σ i)) = σ.sign * M.det :=
((det_row_alternating : alternating_map R (n → R) R n).map_perm M σ).trans
  (by simp [units.smul_def])

/-- Permuting rows and columns with the same equivalence has no effect. -/
@[simp]
lemma det_minor_equiv_self (e : n ≃ m) (A : matrix m m R) :
  det (A.minor e e) = det A :=
begin
  rw [det_apply', det_apply'],
  apply fintype.sum_equiv (equiv.perm_congr e),
  intro σ,
  rw equiv.perm.sign_perm_congr e σ,
  congr' 1,
  apply fintype.prod_equiv e,
  intro i,
  rw [equiv.perm_congr_apply, equiv.symm_apply_apply, minor_apply],
end

/-- Reindexing both indices along the same equivalence preserves the determinant.

For the `simp` version of this lemma, see `det_minor_equiv_self`; this one is unsuitable because
`matrix.reindex_apply` unfolds `reindex` first.
-/
lemma det_reindex_self (e : m ≃ n) (A : matrix m m R) : det (reindex e e A) = det A :=
det_minor_equiv_self e.symm A

/-- The determinant of a permutation matrix equals its sign. -/
@[simp] lemma det_permutation (σ : perm n) :
  matrix.det (σ.to_pequiv.to_matrix : matrix n n R) = σ.sign :=
by rw [←matrix.mul_one (σ.to_pequiv.to_matrix : matrix n n R), pequiv.to_pequiv_mul_matrix,
  det_permute, det_one, mul_one]

@[simp] lemma det_smul (A : matrix n n R) (c : R) : det (c • A) = c ^ fintype.card n * det A :=
calc det (c • A) = det (matrix.mul (diagonal (λ _, c)) A) : by rw [smul_eq_diagonal_mul]
             ... = det (diagonal (λ _, c)) * det A        : det_mul _ _
             ... = c ^ fintype.card n * det A             : by simp [card_univ]

/-- Multiplying each row by a fixed `v i` multiplies the determinant by
the product of the `v`s. -/
lemma det_mul_row (v : n → R) (A : matrix n n R) :
  det (λ i j, v j * A i j) = (∏ i, v i) * det A :=
calc det (λ i j, v j * A i j) = det (A ⬝ diagonal v) : congr_arg det $ by { ext, simp [mul_comm] }
                          ... = (∏ i, v i) * det A : by rw [det_mul, det_diagonal, mul_comm]

/-- Multiplying each column by a fixed `v j` multiplies the determinant by
the product of the `v`s. -/
lemma det_mul_column (v : n → R) (A : matrix n n R) :
  det (λ i j, v i * A i j) = (∏ i, v i) * det A :=
multilinear_map.map_smul_univ _ v A

@[simp]

section hom_map

variables {S : Type w} [comm_ring S]

lemma _root_.ring_hom.map_det (f : R →+* S) (M : matrix n n R) :
  f M.det = matrix.det (f.map_matrix M) :=
by simp [matrix.det_apply', f.map_sum, f.map_prod]

lemma _root_.ring_equiv.map_det (f : R ≃+* S) (M : matrix n n R) :
  f M.det = matrix.det (f.map_matrix M) :=
f.to_ring_hom.map_det _

lemma _root_.alg_hom.map_det [algebra R S] {T : Type z} [comm_ring T] [algebra R T]
  (f : S →ₐ[R] T) (M : matrix n n S) :
  f M.det = matrix.det (f.map_matrix M) :=
f.to_ring_hom.map_det _

lemma _root_.alg_equiv.map_det [algebra R S] {T : Type z} [comm_ring T] [algebra R T]
  (f : S ≃ₐ[R] T) (M : matrix n n S) :
  f M.det = matrix.det (f.map_matrix M) :=
f.to_alg_hom.map_det _

end hom_map

@[simp] lemma det_conj_transpose [star_ring R] (M : matrix m m R) : det (Mᴴ) = star (det M) :=
((star_ring_aut : ring_aut R).map_det _).symm.trans $ congr_arg star M.det_transpose

section det_zero
/-!
### `det_zero` section

Prove that a matrix with a repeated column has determinant equal to zero.
-/

lemma det_eq_zero_of_row_eq_zero {A : matrix n n R} (i : n) (h : ∀ j, A i j = 0) : det A = 0 :=
(det_row_alternating : alternating_map R (n → R) R n).map_coord_zero i (funext h)

lemma det_eq_zero_of_column_eq_zero {A : matrix n n R} (j : n) (h : ∀ i, A i j = 0) : det A = 0 :=
by { rw ← det_transpose, exact det_eq_zero_of_row_eq_zero j h, }

variables {M : matrix n n R} {i j : n}

/-- If a matrix has a repeated row, the determinant will be zero. -/
theorem det_zero_of_row_eq (i_ne_j : i ≠ j) (hij : M i = M j) : M.det = 0 :=
(det_row_alternating : alternating_map R (n → R) R n).map_eq_zero_of_eq M hij i_ne_j

/-- If a matrix has a repeated column, the determinant will be zero. -/
theorem det_zero_of_column_eq (i_ne_j : i ≠ j) (hij : ∀ k, M k i = M k j) : M.det = 0 :=
by { rw [← det_transpose, det_zero_of_row_eq i_ne_j], exact funext hij }

end det_zero

lemma det_update_row_add (M : matrix n n R) (j : n) (u v : n → R) :
  det (update_row M j $ u + v) = det (update_row M j u) + det (update_row M j v) :=
(det_row_alternating : alternating_map R (n → R) R n).map_add M j u v

lemma det_update_column_add (M : matrix n n R) (j : n) (u v : n → R) :
  det (update_column M j $ u + v) = det (update_column M j u) + det (update_column M j v) :=
begin
  rw [← det_transpose, ← update_row_transpose, det_update_row_add],
  simp [update_row_transpose, det_transpose]
end

lemma det_update_row_smul (M : matrix n n R) (j : n) (s : R) (u : n → R) :
  det (update_row M j $ s • u) = s * det (update_row M j u) :=
(det_row_alternating : alternating_map R (n → R) R n).map_smul M j s u

lemma det_update_column_smul (M : matrix n n R) (j : n) (s : R) (u : n → R) :
  det (update_column M j $ s • u) = s * det (update_column M j u) :=
begin
  rw [← det_transpose, ← update_row_transpose, det_update_row_smul],
  simp [update_row_transpose, det_transpose]
end

lemma det_update_row_smul' (M : matrix n n R) (j : n) (s : R) (u : n → R) :
  det (update_row (s • M) j u) = s ^ (fintype.card n - 1) * det (update_row M j u) :=
multilinear_map.map_update_smul _ M j s u

lemma det_update_column_smul' (M : matrix n n R) (j : n) (s : R) (u : n → R) :
  det (update_column (s • M) j u) = s ^ (fintype.card n - 1) * det (update_column M j u) :=
begin
  rw [← det_transpose, ← update_row_transpose, transpose_smul, det_update_row_smul'],
  simp [update_row_transpose, det_transpose]
end

section det_eq

/-! ### `det_eq` section

Lemmas showing the determinant is invariant under a variety of operations.
-/
lemma det_eq_of_eq_mul_det_one {A B : matrix n n R}
  (C : matrix n n R) (hC : det C = 1) (hA : A = B ⬝ C) : det A = det B :=
calc det A = det (B ⬝ C) : congr_arg _ hA
       ... = det B * det C : det_mul _ _
       ... = det B : by rw [hC, mul_one]

lemma det_eq_of_eq_det_one_mul {A B : matrix n n R}
  (C : matrix n n R) (hC : det C = 1) (hA : A = C ⬝ B) : det A = det B :=
calc det A = det (C ⬝ B) : congr_arg _ hA
       ... = det C * det B : det_mul _ _
       ... = det B : by rw [hC, one_mul]

lemma det_update_row_add_self (A : matrix n n R) {i j : n} (hij : i ≠ j) :
  det (update_row A i (A i + A j)) = det A :=
by simp [det_update_row_add,
    det_zero_of_row_eq hij ((update_row_self).trans (update_row_ne hij.symm).symm)]

lemma det_update_column_add_self (A : matrix n n R) {i j : n} (hij : i ≠ j) :
  det (update_column A i (λ k, A k i + A k j)) = det A :=
by { rw [← det_transpose, ← update_row_transpose, ← det_transpose A],
     exact det_update_row_add_self Aᵀ hij }

lemma det_update_row_add_smul_self (A : matrix n n R) {i j : n} (hij : i ≠ j) (c : R) :
  det (update_row A i (A i + c • A j)) = det A :=
by simp [det_update_row_add, det_update_row_smul,
  det_zero_of_row_eq hij ((update_row_self).trans (update_row_ne hij.symm).symm)]

lemma det_update_column_add_smul_self (A : matrix n n R) {i j : n} (hij : i ≠ j) (c : R) :
  det (update_column A i (λ k, A k i + c • A k j)) = det A :=
by { rw [← det_transpose, ← update_row_transpose, ← det_transpose A],
      exact det_update_row_add_smul_self Aᵀ hij c }

lemma det_eq_of_forall_row_eq_smul_add_const_aux
  {A B : matrix n n R} {s : finset n} : ∀ (c : n → R) (hs : ∀ i, i ∉ s → c i = 0)
  (k : n) (hk : k ∉ s) (A_eq : ∀ i j, A i j = B i j + c i * B k j),
  det A = det B :=
begin
  revert B,
  refine s.induction_on _ _,
  { intros A c hs k hk A_eq,
    have : ∀ i, c i = 0,
    { intros i,
      specialize hs i,
      contrapose! hs,
      simp [hs] },
    congr,
    ext i j,
    rw [A_eq, this, zero_mul, add_zero], },
  { intros i s hi ih B c hs k hk A_eq,
    have hAi : A i = B i + c i • B k := funext (A_eq i),
    rw [@ih (update_row B i (A i)) (function.update c i 0), hAi,
        det_update_row_add_smul_self],
    { exact mt (λ h, show k ∈ insert i s, from h ▸ finset.mem_insert_self _ _) hk },
    { intros i' hi',
      rw function.update_apply,
      split_ifs with hi'i, { refl },
      { exact hs i' (λ h, hi' ((finset.mem_insert.mp h).resolve_left hi'i)) } },
    { exact λ h, hk (finset.mem_insert_of_mem h) },
    { intros i' j',
      rw [update_row_apply, function.update_apply],
      split_ifs with hi'i,
      { simp [hi'i] },
      rw [A_eq, update_row_ne (λ (h : k = i), hk $ h ▸ finset.mem_insert_self k s)] } }
end

/-- If you add multiples of row `B k` to other rows, the determinant doesn't change. -/
lemma det_eq_of_forall_row_eq_smul_add_const
  {A B : matrix n n R} (c : n → R) (k : n) (hk : c k = 0)
  (A_eq : ∀ i j, A i j = B i j + c i * B k j) :
  det A = det B :=
det_eq_of_forall_row_eq_smul_add_const_aux c
  (λ i, not_imp_comm.mp $ λ hi, finset.mem_erase.mpr
    ⟨mt (λ (h : i = k), show c i = 0, from h.symm ▸ hk) hi, finset.mem_univ i⟩)
  k (finset.not_mem_erase k finset.univ) A_eq

lemma det_eq_of_forall_row_eq_smul_add_pred_aux {n : ℕ} (k : fin (n + 1)) :
  ∀ (c : fin n → R) (hc : ∀ (i : fin n), k < i.succ → c i = 0)
    {M N : matrix (fin n.succ) (fin n.succ) R}
    (h0 : ∀ j, M 0 j = N 0 j)
    (hsucc : ∀ (i : fin n) j, M i.succ j = N i.succ j + c i * M i.cast_succ j),
    det M = det N :=
begin
  refine fin.induction _ (λ k ih, _) k;
    intros c hc M N h0 hsucc,
  { congr,
    ext i j,
    refine fin.cases (h0 j) (λ i, _) i,
    rw [hsucc, hc i (fin.succ_pos _), zero_mul, add_zero] },

  set M' := update_row M k.succ (N k.succ) with hM',
  have hM : M = update_row M' k.succ (M' k.succ + c k • M k.cast_succ),
  { ext i j,
    by_cases hi : i = k.succ,
    { simp [hi, hM', hsucc, update_row_self] },
    rw [update_row_ne hi, hM', update_row_ne hi] },

  have k_ne_succ : k.cast_succ ≠ k.succ := (fin.cast_succ_lt_succ k).ne,
  have M_k : M k.cast_succ = M' k.cast_succ := (update_row_ne k_ne_succ).symm,

  rw [hM, M_k, det_update_row_add_smul_self M' k_ne_succ.symm, ih (function.update c k 0)],
  { intros i hi,
    rw [fin.lt_iff_coe_lt_coe, fin.coe_cast_succ, fin.coe_succ, nat.lt_succ_iff] at hi,
    rw function.update_apply,
    split_ifs with hik, { refl },
    exact hc _ (fin.succ_lt_succ_iff.mpr (lt_of_le_of_ne hi (ne.symm hik))) },
  { rwa [hM', update_row_ne (fin.succ_ne_zero _).symm] },
  intros i j,
  rw function.update_apply,
  split_ifs with hik,
  { rw [zero_mul, add_zero, hM', hik, update_row_self] },
  rw [hM', update_row_ne ((fin.succ_injective _).ne hik), hsucc],
  by_cases hik2 : k < i,
  { simp [hc i (fin.succ_lt_succ_iff.mpr hik2)] },
  rw update_row_ne,
  apply ne_of_lt,
  rwa [fin.lt_iff_coe_lt_coe, fin.coe_cast_succ, fin.coe_succ, nat.lt_succ_iff, ← not_lt]
end

/-- If you add multiples of previous rows to the next row, the determinant doesn't change. -/
lemma det_eq_of_forall_row_eq_smul_add_pred {n : ℕ}
  {A B : matrix (fin (n + 1)) (fin (n + 1)) R} (c : fin n → R)
  (A_zero : ∀ j, A 0 j = B 0 j)
  (A_succ : ∀ (i : fin n) j, A i.succ j = B i.succ j + c i * A i.cast_succ j) :
  det A = det B :=
det_eq_of_forall_row_eq_smul_add_pred_aux (fin.last _) c
  (λ i hi, absurd hi (not_lt_of_ge (fin.le_last _)))
  A_zero A_succ

/-- If you add multiples of previous columns to the next columns, the determinant doesn't change. -/
lemma det_eq_of_forall_col_eq_smul_add_pred {n : ℕ}
  {A B : matrix (fin (n + 1)) (fin (n + 1)) R} (c : fin n → R)
  (A_zero : ∀ i, A i 0 = B i 0)
  (A_succ : ∀ i (j : fin n), A i j.succ = B i j.succ + c j * A i j.cast_succ) :
  det A = det B :=
by { rw [← det_transpose A, ← det_transpose B],
     exact det_eq_of_forall_row_eq_smul_add_pred c A_zero (λ i j, A_succ j i) }

end det_eq

@[simp] lemma det_block_diagonal {o : Type*} [fintype o] [decidable_eq o] (M : o → matrix n n R) :
  (block_diagonal M).det = ∏ k, (M k).det :=
begin
  -- Rewrite the determinants as a sum over permutations.
  simp_rw [det_apply'],
  -- The right hand side is a product of sums, rewrite it as a sum of products.
  rw finset.prod_sum,
  simp_rw [finset.mem_univ, finset.prod_attach_univ, finset.univ_pi_univ],
  -- We claim that the only permutations contributing to the sum are those that
  -- preserve their second component.
  let preserving_snd : finset (equiv.perm (n × o)) :=
    finset.univ.filter (λ σ, ∀ x, (σ x).snd = x.snd),
  have mem_preserving_snd : ∀ {σ : equiv.perm (n × o)},
    σ ∈ preserving_snd ↔ ∀ x, (σ x).snd = x.snd :=
    λ σ, finset.mem_filter.trans ⟨λ h, h.2, λ h, ⟨finset.mem_univ _, h⟩⟩,
  rw ← finset.sum_subset (finset.subset_univ preserving_snd) _,
  -- And that these are in bijection with `o → equiv.perm m`.
  rw (finset.sum_bij (λ (σ : ∀ (k : o), k ∈ finset.univ → equiv.perm n) _,
                        prod_congr_left (λ k, σ k (finset.mem_univ k))) _ _ _ _).symm,
  { intros σ _,
    rw mem_preserving_snd,
    rintros ⟨k, x⟩,
    simp only [prod_congr_left_apply] },
  { intros σ _,
    rw [finset.prod_mul_distrib, ←finset.univ_product_univ, finset.prod_product_right],
    simp only [sign_prod_congr_left, units.coe_prod, int.cast_prod, block_diagonal_apply_eq,
      prod_congr_left_apply] },
  { intros σ σ' _ _ eq,
    ext x hx k,
    simp only at eq,
    have : ∀ k x, prod_congr_left (λ k, σ k (finset.mem_univ _)) (k, x) =
                  prod_congr_left (λ k, σ' k (finset.mem_univ _)) (k, x) :=
      λ k x, by rw eq,
    simp only [prod_congr_left_apply, prod.mk.inj_iff] at this,
    exact (this k x).1 },
  { intros σ hσ,
    rw mem_preserving_snd at hσ,
    have hσ' : ∀ x, (σ⁻¹ x).snd = x.snd,
    { intro x, conv_rhs { rw [← perm.apply_inv_self σ x, hσ] } },
    have mk_apply_eq : ∀ k x, ((σ (x, k)).fst, k) = σ (x, k),
    { intros k x,
      ext,
      { simp only},
      { simp only [hσ] } },
    have mk_inv_apply_eq : ∀ k x, ((σ⁻¹ (x, k)).fst, k) = σ⁻¹ (x, k),
    { intros k x,
      conv_lhs { rw ← perm.apply_inv_self σ (x, k) },
      ext,
      { simp only [apply_inv_self] },
      { simp only [hσ'] } },
    refine ⟨λ k _, ⟨λ x, (σ (x, k)).fst, λ x, (σ⁻¹ (x, k)).fst, _, _⟩, _, _⟩,
    { intro x,
      simp only [mk_apply_eq, inv_apply_self] },
    { intro x,
      simp only [mk_inv_apply_eq, apply_inv_self] },
    { apply finset.mem_univ },
    { ext ⟨k, x⟩,
      { simp only [coe_fn_mk, prod_congr_left_apply] },
      { simp only [prod_congr_left_apply, hσ] } } },
  { intros σ _ hσ,
    rw mem_preserving_snd at hσ,
    obtain ⟨⟨k, x⟩, hkx⟩ := not_forall.mp hσ,
    rw [finset.prod_eq_zero (finset.mem_univ (k, x)), mul_zero],
    rw [← @prod.mk.eta _ _ (σ (k, x)), block_diagonal_apply_ne],
    exact hkx }
end

/-- The determinant of a 2x2 block matrix with the lower-left block equal to zero is the product of
the determinants of the diagonal blocks. For the generalization to any number of blocks, see
`matrix.upper_block_triangular_det`. -/
lemma upper_two_block_triangular_det
  (A : matrix m m R) (B : matrix m n R) (D : matrix n n R) :
  (matrix.from_blocks A B 0 D).det = A.det * D.det :=
begin
  classical,
  simp_rw det_apply',
  convert
    (sum_subset (subset_univ ((sum_congr_hom m n).range : set (perm (m ⊕ n))).to_finset) _).symm,
  rw sum_mul_sum,
  simp_rw univ_product_univ,
  rw (sum_bij (λ (σ : perm m × perm n) _, equiv.sum_congr σ.fst σ.snd) _ _ _ _).symm,
  { intros σ₁₂ h,
    simp only [],
    erw [set.mem_to_finset, monoid_hom.mem_range],
    use σ₁₂,
    simp only [sum_congr_hom_apply] },
  { simp only [forall_prop_of_true, prod.forall, mem_univ],
    intros σ₁ σ₂,
    rw fintype.prod_sum_type,
    simp_rw [equiv.sum_congr_apply, sum.map_inr, sum.map_inl, from_blocks_apply₁₁,
      from_blocks_apply₂₂],
    have hr : ∀ (a b c d : R), (a * b) * (c * d) = a * c * (b * d), { intros, ac_refl },
    rw hr,
    congr,
    rw [sign_sum_congr, units.coe_mul, int.cast_mul] },
  { intros σ₁ σ₂ h₁ h₂,
    dsimp only [],
    intro h,
    have h2 : ∀ x, perm.sum_congr σ₁.fst σ₁.snd x = perm.sum_congr σ₂.fst σ₂.snd x,
    { intro x, exact congr_fun (congr_arg to_fun h) x },
    simp only [sum.map_inr, sum.map_inl, perm.sum_congr_apply, sum.forall] at h2,
    ext,
    { exact h2.left x },
    { exact h2.right x }},
  { intros σ hσ,
    erw [set.mem_to_finset, monoid_hom.mem_range] at hσ,
    obtain ⟨σ₁₂, hσ₁₂⟩ := hσ,
    use σ₁₂,
    rw ←hσ₁₂,
    simp },
  { intros σ hσ hσn,
    have h1 : ¬ (∀ x, ∃ y, sum.inl y = σ (sum.inl x)),
    { by_contradiction,
      rw set.mem_to_finset at hσn,
      apply absurd (mem_sum_congr_hom_range_of_perm_maps_to_inl _) hσn,
      rintros x ⟨a, ha⟩,
      rw [←ha], exact h a },
    obtain ⟨a, ha⟩ := not_forall.mp h1,
    cases hx : σ (sum.inl a) with a2 b,
    { have hn := (not_exists.mp ha) a2,
      exact absurd hx.symm hn },
    { rw [finset.prod_eq_zero (finset.mem_univ (sum.inl a)), mul_zero],
      rw [hx, from_blocks_apply₂₁], refl }}
end

/-- Laplacian expansion of the determinant of an `n+1 × n+1` matrix along column 0. -/
lemma det_succ_column_zero {n : ℕ} (A : matrix (fin n.succ) (fin n.succ) R) :
  det A = ∑ i : fin n.succ, (-1) ^ (i : ℕ) * A i 0 *
    det (A.minor i.succ_above fin.succ) :=
begin
  rw [matrix.det_apply, finset.univ_perm_fin_succ, ← finset.univ_product_univ],
  simp only [finset.sum_map, equiv.to_embedding_apply, finset.sum_product, matrix.minor],
  refine finset.sum_congr rfl (λ i _, fin.cases _ (λ i, _) i),
  { simp only [fin.prod_univ_succ, matrix.det_apply, finset.mul_sum,
        equiv.perm.decompose_fin_symm_apply_zero, fin.coe_zero, one_mul,
        equiv.perm.decompose_fin.symm_sign, equiv.swap_self, if_true, id.def, eq_self_iff_true,
        equiv.perm.decompose_fin_symm_apply_succ, fin.succ_above_zero, equiv.coe_refl, pow_zero,
        mul_smul_comm] },
  -- `univ_perm_fin_succ` gives a different embedding of `perm (fin n)` into
  -- `perm (fin n.succ)` than the determinant of the submatrix we want,
  -- permute `A` so that we get the correct one.
  have : (-1 : R) ^ (i : ℕ) = i.cycle_range.sign,
  { simp [fin.sign_cycle_range] },
  rw [fin.coe_succ, pow_succ, this, mul_assoc, mul_assoc, mul_left_comm ↑(equiv.perm.sign _),
      ← det_permute, matrix.det_apply, finset.mul_sum, finset.mul_sum],
  -- now we just need to move the corresponding parts to the same place
  refine finset.sum_congr rfl (λ σ _, _),
  rw [equiv.perm.decompose_fin.symm_sign, if_neg (fin.succ_ne_zero i)],
  calc ((-1) * σ.sign : ℤ) • ∏ i', A (equiv.perm.decompose_fin.symm (fin.succ i, σ) i') i'
      = ((-1) * σ.sign : ℤ) • (A (fin.succ i) 0 *
        ∏ i', A (((fin.succ i).succ_above) (fin.cycle_range i (σ i'))) i'.succ) :
    by simp only [fin.prod_univ_succ, fin.succ_above_cycle_range,
      equiv.perm.decompose_fin_symm_apply_zero, equiv.perm.decompose_fin_symm_apply_succ]
  ... = (-1) * (A (fin.succ i) 0 * (σ.sign : ℤ) •
        ∏ i', A (((fin.succ i).succ_above) (fin.cycle_range i (σ i'))) i'.succ) :
    by simp only [mul_assoc, mul_comm, neg_mul_eq_neg_mul_symm, one_mul, zsmul_eq_mul, neg_inj,
      neg_smul, fin.succ_above_cycle_range],
end

/-- Laplacian expansion of the determinant of an `n+1 × n+1` matrix along row 0. -/
lemma det_succ_row_zero {n : ℕ} (A : matrix (fin n.succ) (fin n.succ) R) :
  det A = ∑ j : fin n.succ, (-1) ^ (j : ℕ) * A 0 j *
    det (A.minor fin.succ j.succ_above) :=
by { rw [← det_transpose A, det_succ_column_zero],
     refine finset.sum_congr rfl (λ i _, _),
     rw [← det_transpose],
     simp only [transpose_apply, transpose_minor, transpose_transpose] }

/-- Laplacian expansion of the determinant of an `n+1 × n+1` matrix along row `i`. -/
lemma det_succ_row {n : ℕ} (A : matrix (fin n.succ) (fin n.succ) R) (i : fin n.succ) :
  det A = ∑ j : fin n.succ, (-1) ^ (i + j : ℕ) * A i j *
    det (A.minor i.succ_above j.succ_above) :=
begin
  simp_rw [pow_add, mul_assoc, ← mul_sum],
  have : det A = (-1 : R) ^ (i : ℕ) * (i.cycle_range⁻¹).sign * det A,
  { calc det A = ↑((-1 : units ℤ) ^ (i : ℕ) * (-1 : units ℤ) ^ (i : ℕ) : units ℤ) * det A :
             by simp
           ... = (-1 : R) ^ (i : ℕ) * (i.cycle_range⁻¹).sign * det A :
             by simp [-int.units_mul_self] },
  rw [this, mul_assoc],
  congr,
  rw [← det_permute, det_succ_row_zero],
  refine finset.sum_congr rfl (λ j _, _),
  rw [mul_assoc, matrix.minor, matrix.minor],
  congr,
  { rw [equiv.perm.inv_def, fin.cycle_range_symm_zero] },
  { ext i' j',
    rw [equiv.perm.inv_def, fin.cycle_range_symm_succ] },
end

/-- Laplacian expansion of the determinant of an `n+1 × n+1` matrix along column `j`. -/
lemma det_succ_column {n : ℕ} (A : matrix (fin n.succ) (fin n.succ) R) (j : fin n.succ) :
  det A = ∑ i : fin n.succ, (-1) ^ (i + j : ℕ) * A i j *
    det (A.minor i.succ_above j.succ_above) :=
by { rw [← det_transpose, det_succ_row _ j],
     refine finset.sum_congr rfl (λ i _, _),
     rw [add_comm, ← det_transpose, transpose_apply, transpose_minor, transpose_transpose] }

/-- Determinant of 0x0 matrix -/
@[simp] lemma det_fin_zero {A : matrix (fin 0) (fin 0) R} : det A = 1 :=
det_is_empty

/-- Determinant of 1x1 matrix -/
lemma det_fin_one (A : matrix (fin 1) (fin 1) R) : det A = A 0 0  := det_unique A

/-- Determinant of 2x2 matrix -/
lemma det_fin_two (A : matrix (fin 2) (fin 2) R) :
  det A = A 0 0 * A 1 1 - A 0 1 * A 1 0 :=
begin
  simp [matrix.det_succ_row_zero, fin.sum_univ_succ],
  ring
end

/-- Determinant of 3x3 matrix -/
lemma det_fin_three (A : matrix (fin 3) (fin 3) R) :
  det A = A 0 0 * A 1 1 * A 2 2 - A 0 0 * A 1 2 * A 2 1 - A 0 1 * A 1 0 * A 2 2
  + A 0 1 * A 1 2 * A 2 0 + A 0 2 * A 1 0 * A 2 1 - A 0 2 * A 1 1 * A 2 0 :=
begin
  simp [matrix.det_succ_row_zero, fin.sum_univ_succ],
  ring
end

end matrix


import data.num.basic
set_option pp.structure_projections false

namespace mth1001

section setup

@[derive has_reflect, derive decidable_eq]
inductive mynat : Type
| zero  : mynat
| S : mynat → mynat

open mynat

def add : mynat → mynat → mynat
| a   zero        := a                --     a + zero = a
| a   (S b)    := S (add a b)        -- a + (S b) = S (a + b)

notation a `+` b := add a b

end setup

section addition

open mynat

lemma add_zero  (a : mynat) : a + zero = a := rfl
lemma add_S (a b : mynat) : a + S b = S (a + b) := rfl

-- Exercise 117:
lemma zero_add (a : mynat) :  zero + a = a:=
begin
  induction a with k hk,
  { sorry, },
  { sorry, },
end

-- Exercise 118:
/-
Note that multiple applications of `rw h` (for some lemma or equation `h`)
can be replaced with `repeat {rw h}`.
-/
lemma add_assoc (a b c : mynat) : (a + b) + c = a + (b + c) :=
begin
  induction c with k hk,
  { sorry, },
  { sorry, },
end

-- Exercise 119:
lemma S_add (a b : mynat) : (S a) + b = S (a + b) :=
begin
  sorry
end

-- Exercise 120:
lemma S_eq_add_one (a : mynat) : S a = a + S zero :=
begin
  sorry
end

-- Exercise 121:
lemma add_comm (a b : mynat) : a + b = b + a :=
begin
  sorry
end

end addition

section multiplication

open mynat

-- Next result adapted from data/nat/basic.lean
inductive less_than_or_equal (a : mynat) : mynat → Prop
| refl : less_than_or_equal a
| step : Π {b}, less_than_or_equal b → less_than_or_equal (S b)

def mul : mynat → mynat → mynat
| a   zero      := zero
| a   (S b)  := (mul a b) + a

notation a `*` b := mul a b

open mynat

lemma mul_zero (a : mynat) : a * zero = zero := rfl
lemma mul_S (a b : mynat) : a * (S b) = (a * b) + a := rfl

-- Exercise 122:
lemma zero_mul (a : mynat) : zero * a = zero :=
begin
  sorry
end

-- Exercise 123:
lemma S_mul (a b : mynat) : (S a) * b = a * b + b :=
begin
  sorry
end

-- Exercise 124:
lemma mul_comm (a b : mynat) : a * b = b * a :=
begin
  sorry
end

/-
In the remainder of this section, we prove associativity of multiplication.
-/

-- Exercise 125:
lemma mul_add (a b c : mynat) : a * (b + c) = a * b + a * c :=
begin
  sorry
end

-- Exercise 126:
lemma mul_assoc (a b c : mynat) : (a * b) * c = a * (b * c) :=
begin
  sorry
end

end multiplication

end mth1001


namespace Mt.Utils.Nat

theorem max_of_le {a b : Nat} : a ≤ b → a.max b = b :=by
  intro h ; simp only [Nat.max, h, ite_true]

theorem max_of_eq' {a b : Nat} : a = b → a.max b = b :=
  λ h => max_of_le (by rw [h] ; constructor)

theorem max_of_eq {a b : Nat} : a = b → a.max b = a :=by
  intro h ; rw [max_of_eq' h] ; exact h.symm

theorem max_of_gt {a b : Nat} : a > b → a.max b = a :=by
  intro h ; simp only [Nat.max, Nat.not_le_of_gt h, ite_false]

theorem max_of_ge {a b : Nat} : a ≥ b → a.max b = a :=by
  intro h
  cases Nat.eq_or_lt_of_le h <;> rename_i h
  . exact max_of_eq h.symm
  . exact max_of_gt h

theorem max_of_lt {a b : Nat} : a < b → a.max b = b :=max_of_le ∘ Nat.le_of_lt

theorem max_comm  : ∀ a b : Nat, a.max b = b.max a :=by
  intro a b
  cases Nat.lt_or_ge a b <;> rename_i h
  . rw [max_of_lt h, max_of_gt h]
  cases Nat.eq_or_lt_of_le h <;> (clear h ; rename_i h)
  . rw [max_of_eq h, max_of_eq' h.symm]
  . rw [max_of_lt h, max_of_gt h]

theorem max_assoc : ∀ a b c : Nat, (a.max b).max c = a.max (b.max c) :=by
  intros a b c
  cases Nat.lt_or_ge a b
  <;> cases Nat.lt_or_ge b c
  <;> rename_i ab bc
  . rw [max_of_lt ab, max_of_lt bc, max_of_lt (trans ab bc)]
  . rw [max_of_lt ab, max_of_ge bc, max_of_lt ab]
  . rw [max_of_ge ab, max_of_lt bc]
  . rw [max_of_ge ab, max_of_ge bc, max_of_ge ab]
    exact max_of_ge (trans bc ab : c ≤ a)

theorem zero_max  : ∀ a : Nat, ((0 : Nat).max a) = a :=by
  intro a
  simp only [Nat.max, Nat.zero_le, ite_true]

theorem max_zero  : ∀ a : Nat, (a.max 0) = a :=fun a => calc
  (a.max 0) = Nat.max 0 a :=max_comm ..
          _ = a           :=zero_max a

theorem max_le {a b c : Nat} : a.max b ≤ c ↔ a ≤ c ∧ b ≤ c :=by
  constructor <;> intro h
  <;> (cases Nat.lt_or_ge a b) <;> rename_i ab
  . rw [max_of_lt ab] at h
    exact ⟨Nat.le_of_lt (trans ab h), h⟩
  . rw [max_of_ge ab] at h
    exact ⟨h, trans ab h⟩
  . rw [max_of_lt ab]
    exact h.right
  . rw [max_of_ge ab]
    exact h.left

end Mt.Utils.Nat


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

import data.nat.prime
import data.nat.totient
import algebra.periodic
import data.finset.locally_finite
import data.nat.count
import data.nat.nth

/-!
# The Prime Counting Function

In this file we define the prime counting function: the function on natural numbers that returns
the number of primes less than or equal to its input.

## Main Results

The main definitions for this file are

- `nat.prime_counting`: The prime counting function π
- `nat.prime_counting'`: π(n - 1)

We then prove that these are monotone in `nat.monotone_prime_counting` and
`nat.monotone_prime_counting'`. The last main theorem `nat.prime_counting'_add_le` is an upper
bound on `π'` which arises by observing that all numbers greater than `k` and not coprime to `k`
are not prime, and so only at most `φ(k)/k` fraction of the numbers from `k` to `n` are prime.

## Notation

We use the standard notation `π` to represent the prime counting function (and `π'` to represent
the reindexed version).

-/

namespace nat
open finset

/--
A variant of the traditional prime counting function which gives the number of primes
*strictly* less than the input. More convenient for avoiding off-by-one errors.
-/
def prime_counting' : ℕ → ℕ := nat.count prime

/-- The prime counting function: Returns the number of primes less than or equal to the input. -/
def prime_counting (n : ℕ) : ℕ := prime_counting' (n + 1)

localized "notation `π` := nat.prime_counting" in nat
localized "notation `π'` := nat.prime_counting'" in nat

lemma monotone_prime_counting' : monotone prime_counting' := count_monotone prime

lemma monotone_prime_counting : monotone prime_counting :=
λ a b a_le_b, monotone_prime_counting' (add_le_add_right a_le_b 1)

@[simp] lemma prime_counting'_nth_eq (n : ℕ) : π' (nth prime n) = n :=
count_nth_of_infinite _ infinite_set_of_prime _

@[simp] lemma prime_nth_prime (n : ℕ) : prime (nth prime n) :=
nth_mem_of_infinite _ infinite_set_of_prime _

/-- A linear upper bound on the size of the `prime_counting'` function -/
lemma prime_counting'_add_le {a k : ℕ} (h0 : 0 < a) (h1 : a < k) (n : ℕ) :
  π' (k + n) ≤ π' k + nat.totient a * (n / a + 1) :=
calc π' (k + n)
    ≤ ((range k).filter (prime)).card + ((Ico k (k + n)).filter (prime)).card :
        begin
          rw [prime_counting', count_eq_card_filter_range, range_eq_Ico,
              ←Ico_union_Ico_eq_Ico (zero_le k) (le_self_add), filter_union],
          apply card_union_le,
        end
... ≤ π' k + ((Ico k (k + n)).filter (prime)).card :
        by rw [prime_counting', count_eq_card_filter_range]
... ≤ π' k + ((Ico k (k + n)).filter (coprime a)).card :
        begin
          refine add_le_add_left (card_le_of_subset _) k.prime_counting',
          simp only [subset_iff, and_imp, mem_filter, mem_Ico],
          intros p succ_k_le_p p_lt_n p_prime,
          split,
          { exact ⟨succ_k_le_p, p_lt_n⟩, },
          { rw coprime_comm,
            exact coprime_of_lt_prime h0 (gt_of_ge_of_gt succ_k_le_p h1) p_prime, },
        end
... ≤ π' k + totient a * (n / a + 1) :
        begin
          rw [add_le_add_iff_left],
          exact Ico_filter_coprime_le k n h0,
        end

end nat