section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro hnp,
  contradiction, --tenho p e np nos meus dados
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro hnnp,
  by_cases hp: P, --magia para ganhar p ou np
    exact hp,--tenho p

    have contr := hnnp hp,
    contradiction, -- tenho np e nnp nos meus dados
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    exact doubleneg_elim P,
    exact doubleneg_intro P,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro disj_pq,
  cases disj_pq,--separo minha disj em casos
    right,--escolho-R
      exact disj_pq,
    left,--escolho-L
      exact disj_pq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro conj_pq,
  cases conj_pq with hp hq,--desmembro a conj dos dados
  split,--split'o a conj do alvo
    exact hq,
    exact hp,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro disj_pq,
  intro hp,
  cases disj_pq,--separo minha disj em casos
    have contr := disj_pq hp,--tenho p e np nos meus dados
    contradiction,

    exact disj_pq,--tenho o proprio alvo nos dados
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro disj_pq,
  intro hnp,
  cases disj_pq,--separo minha disj em casos
    have contr := hnp disj_pq,--tenho p e np nos meus dados
    contradiction,

    exact disj_pq,--tenho o proprio alvo nos dados
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro impl_pq,
  intro hnq,
  intro hp,
    have hq := impl_pq hp,
    have contr := hnq hq,
    contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro impl_nqnp,
  intro hp,
  by_cases hq: Q,--magia para q ou nq nos dados
    exact hq,

    have hnp := impl_nqnp hq,
    have contr := hnp hp,
    contradiction,
end

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

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro nlem,
  have lem: P \/ ¬P,--sub demonstracao da lem
    right,
    intro hp,
    have disj_pnp: P \/ ¬P,--sub dem da sub demonstracao da lem
      left,
      exact hp,
    have contr := nlem disj_pnp,
    contradiction,
  have contr := nlem lem,
  contradiction,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro pq_impl_p,
  intro hnp,
  have impl_pq: P →  Q,--sub demonstracao
    intro hp,
    contradiction,--tenho p e np nos meus dados da sub
  have hp := pq_impl_p impl_pq,--obtenho p
  have contr := hnp hp,
  contradiction,--tenho p e np nos meus dados
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro disj_pq,
  intro n_conj_npnq,
  cases n_conj_npnq,--extraio minha conj
  cases disj_pq,--separo minha disj em casos
    have contr := n_conj_npnq_left disj_pq,
    contradiction,
    have contr := n_conj_npnq_right disj_pq,
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro conj_pq,
  intro n_disj_pq,
  cases conj_pq,
  cases n_disj_pq,
    have contr := n_disj_pq conj_pq_left,
    contradiction,
    have contr := n_disj_pq conj_pq_right,
    contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_disj_pq,
  split,
    intro hp,
    --bugou a cabeça

    --intro hq,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro conj_np_nq,
  intro disj_pq,
  cases disj_pq,--separo minha disj em partes
    cases conj_np_nq,--extraio minha conj
      contradiction,
    cases conj_np_nq,
      contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_conj_pq,
  by_cases h: P,
    left,
    intro hq,
    have conj_pq: P /\ Q,
      split,
      exact h,
      exact hq,
    have contr := n_conj_pq conj_pq,
    contradiction,

    right,
    exact h,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro disj_nq_np,
  intro conj_pq,
  cases conj_pq,
  cases disj_nq_np,
    contradiction,
    contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
    exact demorgan_conj P Q,
    exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    exact demorgan_disj 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 conj_pdisj,
  cases conj_pdisj with hp disj,
  cases disj with hq hr,
    left,
    split,
      exact hp,
      exact hq,
    right,
    split,
      exact hp,
      exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro disj_conjs,
  cases disj_conjs with conj_pq conj_pr,
  cases conj_pq with hp hq,
    split,
      exact hp,
      left,
        exact hq,
  cases conj_pr with hp hr,
    split,
    exact hp,
    right,
    exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro disj_conj_pqr,
  split,
    cases disj_conj_pqr with hp conj_qr,
      left,
        exact hp,
      right,
        cases conj_qr with hq hr,
          exact hq,
    cases disj_conj_pqr with hp conj_qr,
      left,
        exact hp,
      right,
        cases conj_qr with hq hr,
          exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro conj_disj,
  cases conj_disj with disj_pq disj_pr,
  cases disj_pq with hp hq,
    left,
      exact hp,
    cases disj_pr with hp hr,
      left,
        exact hp,
      right,
        split,
          exact hq,
          exact hr,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro curry,
  intro hp,
  intro hq,

  have conj_pq: P /\ Q,--sub demonstracao da conj P Q
    split,
      exact hp,
      exact hq,

  have R := curry conj_pq,--consigo o R
    exact R,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro uncurry,
  intro conj_pq,
  cases conj_pq with hp hq,
    have implic := uncurry hp,--uso o P na implicacao P Q R, obtenho implicacao Q R
    have R := implic hq,--agora uso o Q na implicacao Q R obtenho assim o R
    exact R,
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 conj_pq,
  cases conj_pq with hp hq,
  exact hp,
end

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

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  have theor_left := weaken_conj_left P P,
  split,
    exact theor_left,

    intro hp,
    split,
      exact hp,
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro disj_pp,
  cases disj_pp with hp hp,
    exact hp,
    exact hp,
  have theor_right := weaken_disj_left P P,
    exact theor_right,
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 nexist,
  intro hx,
  intro hpx,
  apply nexist,
  existsi hx,
  exact hpx,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with hx hpx,
  have n_hpx := for_all hx,
  have contr := n_hpx hpx,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro n_for_all,
  --

end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with hx hnpx,
  have hpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    exact demorgan_exists U P,
    exact demorgan_exists_converse U P,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with hx hpx,
  have hnpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with hx hnpx,
  have hpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro n_exist,
  intro hx,
  by_cases P hx,
  exact h,
    have exist: ∃(x: U), ¬P x,
    existsi hx,
    exact h,
    have contr := n_exist exist,
    contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro n_for_all,
  by_contradiction n_exist,
  have demorgan_exist := demorgan_exists U P,
  have for_all := demorgan_exist n_exist, --utilizo a implicação
  exact n_for_all for_all,
end

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

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
    exact exists_as_neg_forall U P,
    exact 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
  sorry,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  sorry,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  sorry,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  sorry,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  sorry,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  sorry,
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


def divides (a : Nat) (b : Nat) := ∃ k, a * k = b

infix:40 " | " => divides

theorem divides_def : a | b ↔  ∃ k, a * k = b := Iff.rfl
theorem divides_intro (k: Nat) : a * k = b → a | b := by
  intro h
  apply Exists.intro k
  exact h

theorem all_divide_zero : ∀ n, n | 0 := by
  intro n
  apply divides_intro 0
  simp

theorem divides_refl : ∀ n, n | n := by
  intro n
  apply divides_intro 1
  simp

theorem one_divides_all : ∀ n, 1 | n := by
  intro n
  apply divides_intro n
  simp

def common_divisor (g : Nat) (a: Nat) (b: Nat) := g | a ∧ g | b
def gcd_of (g : Nat) (a: Nat) (b: Nat) := (common_divisor g a b) ∧ ∀ t, common_divisor t a b → t | g
def coprime (a : Nat) (b : Nat) := gcd_of 1 a b
def prime (p : Nat) := 2 ≤ p ∧ ∀ n, n | p → (n = 1 ∨ n = p)

def common_divisor_def : common_divisor g a b ↔ g | a ∧ g | b := Iff.rfl
def common_divisor_intro : g | a → g | b → common_divisor g a b := And.intro
def gcd_of_def : gcd_of g a b ↔ (common_divisor g a b) ∧ ∀ t, common_divisor t a b → t | g := Iff.rfl
def gcd_of_intro : (common_divisor g a b) → (∀ t, common_divisor t a b → t | g) → gcd_of g a b := And.intro
def coprime_def : coprime a b ↔ gcd_of 1 a b := Iff.rfl
def prime_def : prime p ↔ 2 ≤ p ∧ ∀ n, n | p → (n = 1 ∨ n = p) := Iff.rfl
def prime_intro : 2 ≤ p → (∀ n, n | p → (n = 1 ∨ n = p)) → prime p := And.intro

-- TODO: lcm
-- TODO: constructive definition of gcd, lcm would be better
-- TODO: Theorems from the script


/-
From the axioms of reflexivity and substitutabilty
we now prove two *theorems*. A theorem is a proven
proposition. You can even think of the theorem as a
proof of a given proposition. A proof establishes a
conjecture as a theorem. And once it's a theorem,
you can use it like any other inference rule. In
this way, you can build up vast, layered libraries
of theorems. That's what we call mathematics. It's
what mathematicians do.

In this file, we provide both formal and informal
proofs of two theorems:

[1] Equality is symmetric.
[2] Equality is transitive.
-/

/-
To master these concepts, you must appreciate
the distinction between propositions and proofs.
Propositions are "statements of fact" that might
or might not be true. Proofs are objects that
show such propositions to be true. Propositions
that are not true, have no proofs (otherwise of
course they'd be true, as we just said).

So, first, you have to be able to *state* the
conjecture in the formal language of predicate
logic. We'll use an implementation of a specific
version of predicate logic provided by the Lean
Prover.
-/

universe u
def eq_symm : ∀ (α : Sort u) (x y : α), x = y → y = x :=
_
/-
λ α x y h_eq,
  let pf := _ in _ ... ???
_
-/
#check @eq.subst
/-
∀ {α : Sort u_1} {P : α → Prop} {a b : α}, a = b → P a → P b
-/

def eq_symm' : ∀ (α : Sort u) (x y : α), x = y → y = x :=
begin
  intros α x y h_eq,
  rw <-h_eq,
end

axioms (foo bar : ℕ) (h : foo = bar)

def xyz : bar = foo := eq_symm' nat foo bar h
#check xyz

def eq_trans :
  ∀ (a b c : Sort u), a = b → b = c → a = c :=
begin
  assume a b c h_ab h_bc,
  rw <- h_ab at h_bc,
  --exact h_bc,
  --apply h_bc,
  assumption
end

example :
  ∀ (a b c : Sort u), a = b → b = c → a = c :=
begin
  assume a b c h_ab h_bc,
  rw h_ab,
  assumption,
end

/-
Second, you must understand how to construct
proofs of such propositions, if proofs actually
exist, which they might or might not for given
propositions. Not all propositions, statements
asserting "facts", are true. The sky is magenta.
-/

/-
Theorems
-/

-- We define eq_symm to be the proposition at issue
-- Note: Prop is the type of all propositions in Lean
-- And each proposition is itself a type: of it proofs
def eq_symm : Prop :=
  ∀ (T : Type)
    (x y : T),
    x = y →
    y = x

-- We define eq_trans formally in the same basic way
def eq_trans : Prop :=
  ∀ (T : Type)
    (x y z : T),
    x = y →
    y = z →
    x = z

/-
Proofs
-/

/-
In the logic of Lean, a proposition is a type,
and the values of that type, if there are any,
are its proofs.

Just as we can give an example value of a data
type, such as ℕ, we can also give example values
of propositions that are represented as types,
i.e., as "proof terms," or just "proofs."
-/

-- give a value of the nat type
example : ℕ := _

-- give a proof of the proposition 1 = 1
example : 1 = 1 := _

/-
We will now present formal proofs of our two
theorems in this style.
-/

/-
Proof: equality is symmetric.
-/

example : eq_symm :=
begin
  unfold eq_symm, -- replace name with definition
  assume T x y e, -- assume arbitrary values
  rw e,           -- rw uses eq.subst to replace x with y
                  -- and then applies eq.refl automatically
  -- QED.
end

/-
Proof: equality is transitive.
-/

example : eq_trans :=
begin
  unfold eq_trans,
  assume T,
  assume x y z,
  assume e1,
  assume e2,
  rw e1,
  exact e2,
end

/-
A fundamental idea is that any proof is as good as any
other for establishing the truth of a proposition. Here
is an equally good alternative proof (or to be correct,
proof-generating scripts in the "proof tactic" language"
of the Lean Prover.
-/

example : eq_symm :=
begin
  unfold eq_symm,   -- replace name with definition
  assume T x y e,   -- assume arbitrary values
  apply eq.subst e, -- apply axiom 2, substitutability
  exact eq.refl x,  -- apply axiom 1, reflexivity
  -- QED.
end

/-
There. That's a nice proof (script), as it closely
models a typical English language proof. To wit:

Theorem: Equality is symmetric.
Proof: Assume we're given arbitrary values of T,
x, y, and a proof of x = y. What remains to be
proved is y = x. Apply substitutability to write
x as y or y as x. The result is trivially true
by the reflexive property of equality.
-/


import BrownCs22.Library.Defs
import BrownCs22.Library.Tactics
import Mathlib.Data.Nat.ModEq
import Mathlib.Data.Int.GCD

open BrownCs22

/-

Let's check computationally that our RSA algorithm works.

-/

-- given a public key and a modulus `n`, we can encrypt a message.
def rsa_encrypt (public_key : ℕ) (n : ℕ) (message : ℕ) : ℕ :=
  (message ^ public_key) % n

-- given a private key and a modulus `n`, we can decrypt a message.
def rsa_decrypt (private_key : ℕ) (n : ℕ) (encrypted_message : ℕ) : ℕ :=
  (encrypted_message ^ private_key) % n

-- let's choose `n` to be the product of two primes.
def p := 113
def q := 37
def n := p * q

#eval Nat.Prime p
#eval Nat.Prime q

-- We choose our public key that is relatively prime to `(p - 1)*(q - 1)`.
def public_key := 13

#eval Nat.gcd public_key ((p - 1)*(q - 1))

-- Now we need an inverse to the public key mod `(p - 1)*(q - 1)`.
-- We get this from the extended Euclidean algorithm.
#eval Nat.xgcd public_key ((p - 1)*(q - 1))

def private_key := 1861

-- double check it's an inverse
#eval private_key * public_key  % ((p - 1)*(q - 1))

-- Okay! Let's choose a message.
def message := 1034

def encrypted_message := rsa_encrypt public_key n message

#eval encrypted_message

def decrypted_message := rsa_decrypt private_key n encrypted_message

-- encrypting and decrypting the message produces the same output!
#eval decrypted_message

-- We can state, and (mostly) prove, the partial correctness theorem from class!
theorem rsa_correct
  (p q : ℕ) (public_key private_key : ℕ) (message : ℕ)
  (hp : Prime p) (hq : Prime q)
  (h_pub_pri : public_key * private_key ≡ 1 [MOD (p - 1)*(q - 1)])
  (h_msg : message < p*q)
  (h_rel_prime : Nat.gcd msg (p*q) = 1) :

    rsa_decrypt private_key (p*q)
      (rsa_encrypt public_key (p*q) message)
        = message :=
  by
  dsimp [rsa_decrypt, rsa_encrypt]

  have h_lin_combo : ∃ k, public_key * private_key = 1 + (p - 1)*(q - 1)*k :=
    sorry

  eliminate h_lin_combo with k hk

  calc
    (message ^ public_key % (p * q)) ^ private_key % (p * q)
      = (message ^ public_key) ^ private_key % (p * q)      := by rw [← Nat.pow_mod]
    _ = message ^ (public_key * private_key) % (p * q)      := by rw [pow_mul]
    _ = message ^ (1 + (p - 1)*(q - 1)*k) % (p * q)         := by rw [hk]
    _ = message ^ (1 + totient (p*q)*k) % (p * q)           := by sorry
    _ = (message * (message ^ totient (p * q))^k) % (p * q) := by rw [pow_add, pow_one, pow_mul]
    _ = (message % (p * q)) * ((message ^ totient (p * q))^k % (p * q)) % (p * q)
                                                            := by rw [Nat.mul_mod]
    _ = (message % (p * q)) * 1 % (p * q)                   := by sorry
    _ = message % (p * q)                                   := by rw [mul_one, Nat.mod_mod]
    _ = message                                             := by rw [Nat.mod_eq_of_lt h_msg]


import ..lectures.love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe


/-
  Basic constructions involving ideals.

  TODO : Check how much of this isn't actually somehwere in the mathlib.
-/

import data.finset
import linear_algebra.finsupp
import ring_theory.ideal_operations

universes u v

variables {α : Type u} {β : Type v} [comm_ring α] [comm_ring β] (f : α → β) [is_ring_hom f]

def ideal.mk (I : set α) (J : ideal α) (H : I = J) : ideal α :=
{ carrier := I,
  zero := H.symm ▸ J.zero,
  add := H.symm ▸ J.add,
  smul := H.symm ▸ J.smul }

def ker : ideal α :=
ideal.mk {x | f x = 0} (ideal.comap f ⊥) $
set.ext $ λ x, by simp

-- If R is not the zero ring, then the zero ideal is not the whole ring.

lemma zero_ne_one_bot_ne_top : (0 : α) ≠ 1 → (⊥ : ideal α) ≠ ⊤ :=
begin
  intros Hzno,
  have Honz : (1 : α) ∉ ({0} : set α),
    intros HC,
    rw set.mem_singleton_iff at HC,
    exact Hzno HC.symm,
  intros HC,
  replace HC := (ideal.eq_top_iff_one ⊥).1 HC,
  exact (Honz HC),
end

-- Every nonzero ring has a maximal ideal.

lemma ideal.exists_maximal : (0 : α) ≠ 1 → ∃ S : ideal α, ideal.is_maximal S :=
begin
  intros Hzno,
  have HTnB : (⊥ : ideal α) ≠ ⊤ := zero_ne_one_bot_ne_top Hzno,
  rcases (ideal.exists_le_maximal ⊥ HTnB) with ⟨M, ⟨HM, HBM⟩⟩,
  exact ⟨M, HM⟩,
end

-- I need this but I really wish I didn't.

lemma ideal.span_mem_finset {R : Type u} [decidable_eq R] [comm_ring R] (S : finset R) (f : R → R)
: finset.sum S (λ a, f a * a) ∈ (@ideal.span R _ ↑S) :=
begin
  unfold ideal.span,
  apply finset.induction_on S,
  { dsimp; simp, },
  { intros a S Ha IH,
    rw finset.coe_insert,
    rw submodule.mem_span_insert',
    rw finset.sum_insert Ha,
    use [-f a],
    simp [IH], }
end

-- Element in ideal span is a finite linear combination.

lemma ideal.mem_span_iff_total {R : Type u} [comm_ring R] {s : set R} {x : R}:
  x ∈ ideal.span s → ∃ l ∈ finsupp.supported R R s, finsupp.total R R R id l = x :=
begin
  intro H,
  have Hfs := (@finsupp.mem_span_iff_total R _ R _ _ _ id s x).1,
  simp at Hfs,
  rcases (Hfs H) with ⟨l, ⟨Hl, Hlfs⟩⟩,
  use ⟨l, ⟨Hl, Hlfs⟩⟩,
end


/-
Copyright (c) 2022 Violeta Hernández Palacios. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Violeta Hernández Palacios
-/

import data.complex.basic
import data.real.cardinality

/-!
# The cardinality of the complex numbers

This file shows that the complex numbers have cardinality continuum, i.e. `#ℂ = 𝔠`.
-/

open cardinal set

open_locale cardinal

/-- The cardinality of the complex numbers, as a type. -/
@[simp] theorem mk_complex : #ℂ = 𝔠 :=
by rw [mk_congr complex.equiv_real_prod, mk_prod, lift_id, mk_real, continuum_mul_self]

/-- The cardinality of the complex numbers, as a set. -/
@[simp] lemma mk_univ_complex : #(set.univ : set ℂ) = 𝔠 :=
by rw [mk_univ, mk_complex]

/-- The complex numbers are not countable. -/
lemma not_countable_complex : ¬ countable (set.univ : set ℂ) :=
by { rw [← mk_set_le_omega, not_le, mk_univ_complex], apply cantor }


/-
Copyright (c) 2021 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import group_theory.submonoid.operations
import data.fintype.basic

/-!
# Centers of magmas and monoids

## Main definitions

* `set.center`: the center of a magma
* `submonoid.center`: the center of a monoid
* `set.add_center`: the center of an additive magma
* `add_submonoid.center`: the center of an additive monoid

We provide `subgroup.center`, `add_subgroup.center`, `subsemiring.center`, and `subring.center` in
other files.
-/

variables {M : Type*}

namespace set

variables (M)

/-- The center of a magma. -/
@[to_additive add_center /-" The center of an additive magma. "-/]
def center [has_mul M] : set M := {z | ∀ m, m * z = z * m}

@[to_additive mem_add_center]
lemma mem_center_iff [has_mul M] {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

instance decidable_mem_center [has_mul M] [decidable_eq M] [fintype M] :
  decidable_pred (∈ center M) :=
λ _, decidable_of_iff' _ (mem_center_iff M)

@[simp, to_additive zero_mem_add_center]
lemma one_mem_center [mul_one_class M] : (1 : M) ∈ set.center M := by simp [mem_center_iff]

@[simp]
lemma zero_mem_center [mul_zero_class M] : (0 : M) ∈ set.center M := by simp [mem_center_iff]

variables {M}

@[simp, to_additive add_mem_add_center]
lemma mul_mem_center [semigroup M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a * b ∈ set.center M :=
λ g, by rw [mul_assoc, ←hb g, ← mul_assoc, ha g, mul_assoc]

@[simp, to_additive neg_mem_add_center]
lemma inv_mem_center [group M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
λ g, by rw [← inv_inj, mul_inv_rev, inv_inv, ← ha, mul_inv_rev, inv_inv]

@[simp]
lemma add_mem_center [distrib M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a + b ∈ set.center M :=
λ c, by rw [add_mul, mul_add, ha c, hb c]

@[simp]
lemma neg_mem_center [ring M] {a : M} (ha : a ∈ set.center M) : -a ∈ set.center M :=
λ c, by rw [←neg_mul_comm, ha (-c), neg_mul_comm]

@[to_additive subset_add_center_add_units]
lemma subset_center_units [monoid M] :
  (coe : units M → M) ⁻¹' center M ⊆ set.center (units M) :=
λ a ha b, units.ext $ ha _

lemma center_units_subset [group_with_zero M] :
  set.center (units M) ⊆ (coe : units M → M) ⁻¹' center M :=
λ a ha b, begin
  obtain rfl | hb := eq_or_ne b 0,
  { rw [zero_mul, mul_zero], },
  { exact units.ext_iff.mp (ha (units.mk0 _ hb)) }
end

/-- In a group with zero, the center of the units is the preimage of the center. -/
lemma center_units_eq [group_with_zero M] :
  set.center (units M) = (coe : units M → M) ⁻¹' center M :=
subset.antisymm center_units_subset subset_center_units

@[simp]
lemma inv_mem_center₀ [group_with_zero M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
begin
  obtain rfl | ha0 := eq_or_ne a 0,
  { rw inv_zero, exact zero_mem_center M },
  lift a to units M using ha0,
  rw ←units.coe_inv',
  exact center_units_subset (inv_mem_center (subset_center_units ha)),
end

@[simp, to_additive sub_mem_add_center]
lemma div_mem_center [group M] {a b : M} (ha : a ∈ set.center M) (hb : b ∈ set.center M) :
  a / b ∈ set.center M :=
begin
  rw [div_eq_mul_inv],
  exact mul_mem_center ha (inv_mem_center hb),
end

@[simp]
lemma div_mem_center₀ [group_with_zero M] {a b : M} (ha : a ∈ set.center M)
  (hb : b ∈ set.center M) : a / b ∈ set.center M :=
begin
  rw div_eq_mul_inv,
  exact mul_mem_center ha (inv_mem_center₀ hb),
end

variables (M)

@[simp, to_additive add_center_eq_univ]
lemma center_eq_univ [comm_semigroup M] : center M = set.univ :=
subset.antisymm (subset_univ _) $ λ x _ y, mul_comm y x

end set

namespace submonoid
section
variables (M) [monoid M]

/-- The center of a monoid `M` is the set of elements that commute with everything in `M` -/
@[to_additive "The center of a monoid `M` is the set of elements that commute with everything in
`M`"]
def center : submonoid M :=
{ carrier := set.center M,
  one_mem' := set.one_mem_center M,
  mul_mem' := λ a b, set.mul_mem_center }

@[to_additive] lemma coe_center : ↑(center M) = set.center M := rfl

variables {M}

@[to_additive] lemma mem_center_iff {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

instance decidable_mem_center [decidable_eq M] [fintype M] : decidable_pred (∈ center M) :=
λ _, decidable_of_iff' _ mem_center_iff

/-- The center of a monoid is commutative. -/
instance : comm_monoid (center M) :=
{ mul_comm := λ a b, subtype.ext $ b.prop _,
  .. (center M).to_monoid }

end

section
variables (M) [comm_monoid M]

@[simp] lemma center_eq_top : center M = ⊤ :=
set_like.coe_injective (set.center_eq_univ M)

end

end submonoid


import .love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe


import Mathlib
/-!
# Examples of Proofs

We see our first proofs, most of which involve the `≤` relation on natural numbers.

We will see that the natural numbers are "defined" in terms of the `zero` and `succ` constructors.

* `Nat.zero : ℕ`
* `Nat.succ : ℕ → ℕ`

Analogous to this, (modulo renaming) the `≤` relation is defined in terms of the `le_refl` and `le_step` constructors.

* `Nat.le : ℕ → ℕ → Prop`
* `Nat.le_refl : ∀ n : ℕ, n ≤ n`
* `Nat.le_step : ∀ {n m : ℕ}, n ≤ m → n ≤ Nat.succ m`

However, we will first focus on a simpler relation.
-/

#check Nat.zero
#check Nat.succ

#check Nat.le
#check Nat.le_step
#check Nat.le_refl

/-!
### Example: a proof by reflexivity of equality

Our first example is simpler: just a proof of equality. Note that a proof of a theorem in lean is very similar, both conceptually and syntactically, to a definition.

In a definition, we give a name, a type and a value (though sometimes the type is inferred so can be omitted). In a theorem, we give a name, a statement and a proof. The statement is essentially a type, and the proof is a value of that type.

Technically a statement is a proposition which is a special sort of type as we see below, but in these examples it is best to think of it as just a type.
-/

/-- The theorem that `1 + 1 = 2` -/
theorem one_plus_one_is_two : 1 + 1 = 2 := rfl

/-!
We look more closely at the right hand side, i.e., the _proof_. Below is the description in Lean of `rfl`, which corresponds to _reflexivity of equality_, i.e., the fact that any term is equal to itself:

> `rfl : a = a` is the unique constructor of the equality type. This is the same as `Eq.refl` except that it takes `a` implicitly instead of explicitly.

> This is a more powerful theorem than it may appear at first, because although the statement of the theorem is `a = a`, lean will allow anything that is definitionally equal to that type. So, for instance, `2 + 2 = 4` is proven in lean by `rfl`, because both sides are the same up to definitional equality.

To clarify what is meant by _implicit_ and _explicit_ parameters, we look at the types of `rfl` and `Eq.refl`. Both  these represent the statement that if `α` is a type and `a : α` is a term of type `α` then `a = a`.

```lean
#check rfl  -- rfl.{u} {α : Sort u} {a : α} : a = a

#check Eq.refl -- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a
```

Notice that in `rfl` _both_ the arguments `α` and `a` are in braces while in `Eq.refl` the argument `α` is in braces while `a` in in parenthesis. When calling a function, the arguments in parenthesis are to be specified, while those in braces are to be _inferred_ (if possible).

If we prepend `@` to a function name then all arguments become explicit. Conversely we can ask Lean to infer arguments by using `_`. The following examples of similar proofs should clarify these.

```lean
/- argument `a` given explicitly -/
example : 2 = 2 := Eq.refl 2

/- notation `@` used and both `α` and `a` are given explicitly-/
example : 3 = 3 := @Eq.refl ℕ 3

/- the argument `a` is inferred-/
example : 1 + 2 = 3 := Eq.refl _

/- notation `@` used but `α` is inferred-/
example : 3  = 3 := @Eq.refl _ 3
```

### Incorrect proofs

Of course the right hand side may not be a proof of the statement in the left hand side. As mentioned above, this is equivalent to the term on the right-hand side having the _type_ specified in the left hand side.

For example, consdier the following (incorrect) proof.
```lean
theorem wrong : 1 + 2 = 2 := rfl
```

We get the following error message:
```lean
type mismatch
  rfl
has type
  1 + 2 = 1 + 2 : Prop
but is expected to have type
  1 + 2 = 2 : Prop
```

This is because Lean inferred that `a` is `1 + 2` (and `α` is `ℕ`) and deduced that the type of the right hand side is `1 + 2 = 1 + 2`. This is not equal to the type specified in the left hand side, `1 + 2 = 2`.

We get a different type of error if the statement we are trying to prove is not an equality. For example, consider the following (incorrect) proof.
```lean
example : 1 ≤ 2 := rfl
```

Here Lean is not able to infer `a` (or even `α`) and so we get the following error message:

```lean
type mismatch
  rfl
has type
  ?m.690 = ?m.690 : Prop
but is expected to have type
  1 ≤ 2 : Prop
```

All that Lean knows about `rfl` is that it is a proof that `a = a`, but the statement is not an equality. So Lean assign a _metavariable_ `?m.690` to `a` and cannot _solve_ for the metavariable, giving an error as above.
-/

#check rfl  -- rfl.{u} {α : Sort u} {a : α} : a = a

#check Eq.refl -- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a

-- example : 2 = 2 := 2 = 2

-- theorem wrong : 1 + 2 = 2 := rfl

example : 1 + 2 = 3 := Eq.refl _

-- example : 1 ≤ 2 := rfl

-- def not_a_nat : ℕ := "you cannot fool me"

/-!
### Proofs versus Definitions; Sorts and Types

To clarify the similarities and differences between a definition and a theorem, as well as sorts and types, we look at the following example.
-/
/-- Definition of `two` for comparison -/
def two : ℕ := 2
-- def two' : ℕ := ℕ
/-!
As we have remarked, this is formally very similar to the proof of a theorem. To see the differences, we see some associated types:

```lean
#check 1 = 1 -- 1 = 1 : Prop
#check ℕ -- ℕ : Type

/- The universe of propositions. Prop ≡ Sort 0-/
#check Prop -- Prop : Type

/- A type universe. Type ≡ Type 0, Type u ≡ Sort (u + 1)-/
#check Type -- Type : Type 1
```

The main difference between the two cases is that the type of a theorem is a _proposition_, which is a special _type_ (i.e., a type in `Prop`). The type of `Prop` is `Type`, which is a type in the next universe up and is the type of `ℕ`. The universes form so called _Sorts_, with all but the first being _Types_.

In other closely related foundational systems (such as _Homotopy Type Theory_), this distinction is not present and all uninverses are _Types_. In Lean _propositions_, i.e., elements of `Prop` (such as `1 = 1`) have a special property that two terms (i.e., proofs) of a proposition are equal by definition.

Propositions are taken to be at "Types at level -1", i.e.,  "Sorts at level 0". This makes it convenient to exclude propositions in some cases where it is necessary to do so (as we will see later).
-/

example : 2 = 2 := Eq.refl 2

example : 3 = 3 := @Eq.refl ℕ 3

example : 3  = 3 := @Eq.refl _ 3

#check 1 = 1 -- 1 = 1 : Prop
#check ℕ -- ℕ : Type

/- The universe of propositions. Prop ≡ Sort 0-/
#check Prop -- Prop : Type

/- A type universe. Type ≡ Type 0, Type u ≡ Sort (u + 1)-/
#check Type -- Type : Type 1


/-
Copyright (c) 2020 The Xena project. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Kevin Buzzard
Thanks: Imperial College London, leanprover-community

The complex numbers, modelled as R^2 in the obvious way.
-/
import complex.basic -- tutorial level
/-!

# Level 1: I

I find it unbelievable that we have written quite a lot of code about the complex numbers
and we've still never defined i, or j, or I, or $$\sqrt{-1}$$, or whatever it's called.
Why don't you supply the definition, and make the basic API?

All the proofs below are sorried. You can try them in tactic mode
by replacing `sorry` with `begin end` and then starting to write
tactics in the `begin end` block.
-/

namespace complex

/-- complex.I is the square root of -1 above the imaginary axis -/
def I : ℂ := ⟨0, 1⟩

/-

 Easy lemmas, tagged with `simp` so Lean can prove things about `I` by equating
 real and imaginary parts.

-/

/-- re(I) = 0 -/
@[simp] lemma I_re : re(I) = 0 :=
begin
  refl
end

/-- im(I) = 1 -/
@[simp] lemma I_im : im(I) = 1 :=
begin
  refl
end

/-- I*I = -1 -/
@[simp] lemma I_mul_I : I * I = -1 :=
begin
  -- suffices to check real and imaginary parts
  ext;
  -- do them both at once
  simp,
end

-- Boss level. Hint: don't forget ext_iff

/-- I is non-zero -/
lemma I_ne_zero : (I : ℂ) ≠ 0 :=
begin
  -- by contradiction
  intro h,
  -- what does it mean for two complex numbers to be equal
  rw ext_iff at h,
  -- this is now just a logic puzzle
  simp * at *,
end

end complex


-- La_congruencia_modulo_2_es_una_relacion_de_equivalencia.lean
-- La congruencia módulo 2 es una relación de equivalencia
-- José A. Alonso Jiménez
-- Sevilla, 1 de agosto de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Se define la relación R entre los números enteros de forma que x está
-- relacionado con y si x-y es divisible por 2. Demostrar que R es una
-- relación de equivalencia.
-- ---------------------------------------------------------------------

import data.int.basic
import tactic

def R (m n : ℤ) := 2 ∣ (m - n)

-- 1ª demostración
example : equivalence R :=
begin
  repeat {split},
  { intro x,
    unfold R,
    rw sub_self,
    exact dvd_zero 2, },
  { intros x y hxy,
    unfold R,
    cases hxy with a ha,
    use -a,
    calc y - x
         = -(x - y) : (neg_sub x y).symm
     ... = -(2 * a) : by rw ha
     ... = 2 * -a   : neg_mul_eq_mul_neg 2 a, },
  { intros x y z hxy hyz,
    cases hxy with a ha,
    cases hyz with b hb,
    use a + b,
    calc x - z
         = (x - y) + (y - z) : (sub_add_sub_cancel x y z).symm
     ... = 2 * a + 2 * b     : congr_arg2 ((+)) ha hb
     ... = 2 * (a + b)       : (mul_add 2 a b).symm , },
end

-- 2ª demostración
example : equivalence R :=
begin
  repeat {split},
  { intro x,
    simp [R], },
  { rintros x y ⟨a, ha⟩,
    use -a,
    linarith, },
  { rintros x y z ⟨a, ha⟩ ⟨b, hb⟩,
    use a + b,
    linarith, },
end


/-Proof of the irrationality of root 2 using natural numbers

NOTE : in order to easily access lemmas which I did not the names of,
I used 'library_search' and 'suggest' tactics. The library, mathlib,
contains lemmas have been built up from the axioms by members of the lean community
and are consistent with the axioms of the specific number system and also
of the logical system.

Here is an example of such a theorem:

--theorem modeq_mul_left (a b n c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD n] :=
--modeq_of_dvd_of_modeq (dvd_mul_left _ _) $ modeq_mul_left' _ h

I also used simplifiers such as norm_num that can perform basic elementary arithmetic, or
linarith which simplifies inequalities, which help the process somewhat, and prevent the users from having to
recall Peano's axioms every time.
-/

/-First we can import solely the lemmas we want from the library.
For our proof we are concerned with natural numbers, mods, and parities.-/

import tactic.suggest
import data.nat.basic
import data.nat.modeq
import data.nat.parity
import data.nat.gcd

/- While working in namespace, we can refer to identifiers by their shorter
names. For example, here nat.even can simply be written as even. -/
namespace nat
namespace modeq

---Let us start off with a relatively straightforward proof, using forward reasoning.
lemma even_squares_are_multiples_of_4 (x : ℕ ) : x.even → 4 ∣ (x * x) :=
begin
  intros h, --introduces the premise as a hypothesis
  unfold even at h, --mathlib has already defined even numbers in this way; def even (n : nat) : Prop := 2 ∣ n
  exact mul_dvd_mul h h, --exact matches a hypothesis with the desired result; if there is a match, then the proof is complete.
end --when a proof is verified, readers should see 'goals accomplished' in the Lean infoview.

/-In fact, we can make the above proof only one line long. This makes it harder to understand
but its concision is beneficial when compiling a library of such lemmas.
Note that it does not have the begin end structure, meaning that it does not use any
tactics, and this is called a proof term. In fact, this syntax is very similar to
what Lean stores behind the scenes.-/
lemma even_squares_are_multiples_of_4_shorter (x : ℕ ) : x.even → 4 ∣ (x * x) :=
λ x, mul_dvd_mul x x --note the λ from lambda calculus

--Now let us verify that the roots of all even square numbers are even.
lemma root_of_even_is_even (x s : ℕ ) : even (x * x) →  even x :=
begin
  contrapose, --applies a logical theorem that says {p → q} ↔ {not q → not p} in lean 'not' is ¬
  intros h,
  rw not_even_iff at h, /-rw stands for rewrite, and can be used to replace equivalent statements, such as iff-/
  --imported lemma: not_even_iff {n : ℕ} : ¬ even n ↔ n % 2 = 1 :=
  --(meaning not even numbers are 1 mod 2)
  rw not_even_iff,
  have i : x ≡ 1 [MOD 2],/-the have tactic allows us to introduce a hypothesis, which is accepted as such if
  we can prove it within the existing hypotheses we have. We do this in the subsequent curly brackets{}-/
  --Essentially here we are trying to convert h into a usable form for theorem
  {exact h,},
  have j : (x * x ) ≡ x * 1 [MOD 2], --again converting into a usable form for theorem
  {apply modeq_mul_left, /-applied imported theorem modeq_mul_left (c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD n] :=
  modeq_of_dvd_of_modeq (dvd_mul_left _ _) $ modeq_mul_left' _ h-/
  /-the apply tactic is a form of backwards reasoning that replaces the
  goal with a premise from a lemma or hypothesis that would need to be proven instead-/
  exact i,},
  simp at j, --simplifier tactic
  have k : x * x ≡ 1 [MOD 2],
  {calc x * x ≡ x [MOD 2] : by apply j
         ... ≡ 1 [MOD 2] : by apply i,},
  exact k,
end

/- The structure of the following proof consists mostly of forwards reasoning where we
introduce an easily provable hypothesis by the tactic have { }, eventually
deriving the hypotheses we need for the final tautology, with what we hope
to be a natural flavour.-/

/- Now we finally have the tools to prove the irrationality of root 2.
This is equivalent to saying that we cannot write root 2 as an irreduicible fraction
a/b where a and b are natural numbers. So a and b must be coprime.-/

lemma root_two_irrational (a b d : ℕ ) : (∀ a b, nat.coprime a b) → (∃ a b, a * a = 2 * b * b) → false :=
begin
  intros p e,
  cases e with a ha, --removes the existential quantifier ∃ by specifying an a
  cases ha with b hb,
  have eaa : even (a * a),
  {use (b * b),
  rw ← mul_assoc,
  exact hb,},
  have ea : even a,
  {apply root_of_even_is_even, --our own lemma, now in usable form
  exact a,
  exact eaa,},
  have foura : 4 ∣ a * a,
  {apply even_squares_are_multiples_of_4, --our own lemma
  exact ea,},
  rw hb at foura,
  have fourb : 2 * 2 ∣ 2 * (b * b),
  {rw ← mul_assoc,
  norm_num,
  exact foura,},
  have ebb : even (b * b),
  {unfold even,
  rw nat.mul_dvd_mul_iff_left _ at fourb,/-imported nat.mul_dvd_mul_iff_left : ∀ {a b c : ℕ},
  0 < a → (a * b ∣ a * c ↔ b ∣ c)-/
  exact fourb,
  norm_num,},
  have eb : even b,
  {apply root_of_even_is_even,
   exact b,
   exact ebb,},
  unfold even at ea eb,
  have : 2 ∣ gcd a b,
  {apply dvd_gcd,
  exact ea,
  exact eb,},
  unfold nat.coprime at p,
  rw p at this,
  have that : ¬ 2 ∣ 1,
  {norm_num,},--another simplifier tactic
  tauto, --short for a tautology → false
end

end modeq
end nat

/-Notice that we have not explicitly been able to define the irrational numbers, but there exists
a section of the library which has already done this, and we leave it here for the reader's
interest:

/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Yury Kudryashov.
-/

def irrational (x : ℝ) := x ∉ set.range (coe : ℚ → ℝ)

/- If `x^n`, `n > 0`, is integer and is not the `n`-th power of an integer, then
`x` is irrational. -/

theorem irrational_nrt_of_notint_nrt {x : ℝ} (n : ℕ) (m : ℤ)
  (hxr : x ^ n = m) (hv : ¬ ∃ y : ℤ, x = y) (hnpos : 0 < n) :
  irrational x :=
begin
  rintros ⟨⟨N, D, P, C⟩, rfl⟩,
  rw [← cast_pow] at hxr,
  have c1 : ((D : ℤ) : ℝ) ≠ 0,
  { rw [int.cast_ne_zero, int.coe_nat_ne_zero], exact ne_of_gt P },
  have c2 : ((D : ℤ) : ℝ) ^ n ≠ 0 := pow_ne_zero _ c1,
  rw [num_denom', cast_pow, cast_mk, div_pow, div_eq_iff_mul_eq c2,
      ← int.cast_pow, ← int.cast_pow, ← int.cast_mul, int.cast_inj] at hxr,
  have hdivn : ↑D ^ n ∣ N ^ n := dvd.intro_left m hxr,
  rw [← int.dvd_nat_abs, ← int.coe_nat_pow, int.coe_nat_dvd, int.nat_abs_pow,
    nat.pow_dvd_pow_iff hnpos] at hdivn,
  have hD : D = 1 := by rw [← nat.gcd_eq_right hdivn, C.gcd_eq_one],
  subst D,
  refine hv ⟨N, _⟩,
  rw [num_denom', int.coe_nat_one, mk_eq_div, int.cast_one, div_one, cast_coe_int]
end
-/


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

! This file was ported from Lean 3 source module analysis.special_functions.complex.log
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.SpecialFunctions.Complex.Arg
import Mathbin.Analysis.SpecialFunctions.Log.Basic

/-!
# The complex `log` function

Basic properties, relationship with `exp`.
-/

noncomputable section

namespace Complex

open Set Filter

open Real Topology ComplexConjugate

/-- Inverse of the `exp` function. Returns values such that `(log x).im > - π` and `(log x).im ≤ π`.
  `log 0 = 0`-/
@[pp_nodot]
noncomputable def log (x : ℂ) : ℂ :=
  x.abs.log + arg x * I
#align complex.log Complex.log

theorem log_re (x : ℂ) : x.log.re = x.abs.log := by simp [log]
#align complex.log_re Complex.log_re

theorem log_im (x : ℂ) : x.log.im = x.arg := by simp [log]
#align complex.log_im Complex.log_im

theorem neg_pi_lt_log_im (x : ℂ) : -π < (log x).im := by simp only [log_im, neg_pi_lt_arg]
#align complex.neg_pi_lt_log_im Complex.neg_pi_lt_log_im

theorem log_im_le_pi (x : ℂ) : (log x).im ≤ π := by simp only [log_im, arg_le_pi]
#align complex.log_im_le_pi Complex.log_im_le_pi

theorem exp_log {x : ℂ} (hx : x ≠ 0) : exp (log x) = x := by
  rw [log, exp_add_mul_I, ← of_real_sin, sin_arg, ← of_real_cos, cos_arg hx, ← of_real_exp,
    Real.exp_log (abs.pos hx), mul_add, of_real_div, of_real_div,
    mul_div_cancel' _ (of_real_ne_zero.2 <| abs.ne_zero hx), ← mul_assoc,
    mul_div_cancel' _ (of_real_ne_zero.2 <| abs.ne_zero hx), re_add_im]
#align complex.exp_log Complex.exp_log

@[simp]
theorem range_exp : range exp = {0}ᶜ :=
  Set.ext fun x =>
    ⟨by
      rintro ⟨x, rfl⟩
      exact exp_ne_zero x, fun hx => ⟨log x, exp_log hx⟩⟩
#align complex.range_exp Complex.range_exp

theorem log_exp {x : ℂ} (hx₁ : -π < x.im) (hx₂ : x.im ≤ π) : log (exp x) = x := by
  rw [log, abs_exp, Real.log_exp, exp_eq_exp_re_mul_sin_add_cos, ← of_real_exp,
    arg_mul_cos_add_sin_mul_I (Real.exp_pos _) ⟨hx₁, hx₂⟩, re_add_im]
#align complex.log_exp Complex.log_exp

theorem exp_inj_of_neg_pi_lt_of_le_pi {x y : ℂ} (hx₁ : -π < x.im) (hx₂ : x.im ≤ π) (hy₁ : -π < y.im)
    (hy₂ : y.im ≤ π) (hxy : exp x = exp y) : x = y := by
  rw [← log_exp hx₁ hx₂, ← log_exp hy₁ hy₂, hxy]
#align complex.exp_inj_of_neg_pi_lt_of_le_pi Complex.exp_inj_of_neg_pi_lt_of_le_pi

theorem of_real_log {x : ℝ} (hx : 0 ≤ x) : (x.log : ℂ) = log x :=
  Complex.ext (by rw [log_re, of_real_re, abs_of_nonneg hx])
    (by rw [of_real_im, log_im, arg_of_real_of_nonneg hx])
#align complex.of_real_log Complex.of_real_log

theorem log_of_real_re (x : ℝ) : (log (x : ℂ)).re = Real.log x := by simp [log_re]
#align complex.log_of_real_re Complex.log_of_real_re

theorem log_of_real_mul {r : ℝ} (hr : 0 < r) {x : ℂ} (hx : x ≠ 0) :
    log (r * x) = Real.log r + log x :=
  by
  replace hx := complex.abs.ne_zero_iff.mpr hx
  simp_rw [log, map_mul, abs_of_real, arg_real_mul _ hr, abs_of_pos hr, Real.log_mul hr.ne' hx,
    of_real_add, add_assoc]
#align complex.log_of_real_mul Complex.log_of_real_mul

theorem log_mul_of_real (r : ℝ) (hr : 0 < r) (x : ℂ) (hx : x ≠ 0) :
    log (x * r) = Real.log r + log x := by rw [mul_comm, log_of_real_mul hr hx, add_comm]
#align complex.log_mul_of_real Complex.log_mul_of_real

@[simp]
theorem log_zero : log 0 = 0 := by simp [log]
#align complex.log_zero Complex.log_zero

@[simp]
theorem log_one : log 1 = 0 := by simp [log]
#align complex.log_one Complex.log_one

theorem log_neg_one : log (-1) = π * I := by simp [log]
#align complex.log_neg_one Complex.log_neg_one

theorem log_i : log I = π / 2 * I := by simp [log]
#align complex.log_I Complex.log_i

theorem log_neg_i : log (-I) = -(π / 2) * I := by simp [log]
#align complex.log_neg_I Complex.log_neg_i

theorem log_conj_eq_ite (x : ℂ) : log (conj x) = if x.arg = π then log x else conj (log x) :=
  by
  simp_rw [log, abs_conj, arg_conj, map_add, map_mul, conj_of_real]
  split_ifs with hx
  · rw [hx]
  simp_rw [of_real_neg, conj_I, mul_neg, neg_mul]
#align complex.log_conj_eq_ite Complex.log_conj_eq_ite

theorem log_conj (x : ℂ) (h : x.arg ≠ π) : log (conj x) = conj (log x) := by
  rw [log_conj_eq_ite, if_neg h]
#align complex.log_conj Complex.log_conj

theorem log_inv_eq_ite (x : ℂ) : log x⁻¹ = if x.arg = π then -conj (log x) else -log x :=
  by
  by_cases hx : x = 0
  · simp [hx]
  rw [inv_def, log_mul_of_real, Real.log_inv, of_real_neg, ← sub_eq_neg_add, log_conj_eq_ite]
  · simp_rw [log, map_add, map_mul, conj_of_real, conj_I, norm_sq_eq_abs, Real.log_pow,
      Nat.cast_two, of_real_mul, of_real_bit0, of_real_one, neg_add, mul_neg, two_mul, neg_neg]
    split_ifs
    · rw [add_sub_right_comm, sub_add_cancel']
    · rw [add_sub_right_comm, sub_add_cancel']
  · rwa [inv_pos, Complex.normSq_pos]
  · rwa [map_ne_zero]
#align complex.log_inv_eq_ite Complex.log_inv_eq_ite

theorem log_inv (x : ℂ) (hx : x.arg ≠ π) : log x⁻¹ = -log x := by rw [log_inv_eq_ite, if_neg hx]
#align complex.log_inv Complex.log_inv

theorem two_pi_i_ne_zero : (2 * π * I : ℂ) ≠ 0 := by norm_num [Real.pi_ne_zero, I_ne_zero]
#align complex.two_pi_I_ne_zero Complex.two_pi_i_ne_zero

theorem exp_eq_one_iff {x : ℂ} : exp x = 1 ↔ ∃ n : ℤ, x = n * (2 * π * I) :=
  by
  constructor
  · intro h
    rcases existsUnique_add_zsmul_mem_Ioc Real.two_pi_pos x.im (-π) with ⟨n, hn, -⟩
    use -n
    rw [Int.cast_neg, neg_mul, eq_neg_iff_add_eq_zero]
    have : (x + n * (2 * π * I)).im ∈ Ioc (-π) π := by simpa [two_mul, mul_add] using hn
    rw [← log_exp this.1 this.2, exp_periodic.int_mul n, h, log_one]
  · rintro ⟨n, rfl⟩
    exact (exp_periodic.int_mul n).Eq.trans exp_zero
#align complex.exp_eq_one_iff Complex.exp_eq_one_iff

theorem exp_eq_exp_iff_exp_sub_eq_one {x y : ℂ} : exp x = exp y ↔ exp (x - y) = 1 := by
  rw [exp_sub, div_eq_one_iff_eq (exp_ne_zero _)]
#align complex.exp_eq_exp_iff_exp_sub_eq_one Complex.exp_eq_exp_iff_exp_sub_eq_one

theorem exp_eq_exp_iff_exists_int {x y : ℂ} : exp x = exp y ↔ ∃ n : ℤ, x = y + n * (2 * π * I) := by
  simp only [exp_eq_exp_iff_exp_sub_eq_one, exp_eq_one_iff, sub_eq_iff_eq_add']
#align complex.exp_eq_exp_iff_exists_int Complex.exp_eq_exp_iff_exists_int

@[simp]
theorem countable_preimage_exp {s : Set ℂ} : (exp ⁻¹' s).Countable ↔ s.Countable :=
  by
  refine' ⟨fun hs => _, fun hs => _⟩
  · refine' ((hs.image exp).insert 0).mono _
    rw [image_preimage_eq_inter_range, range_exp, ← diff_eq, ← union_singleton, diff_union_self]
    exact subset_union_left _ _
  · rw [← bUnion_preimage_singleton]
    refine' hs.bUnion fun z hz => _
    rcases em (∃ w, exp w = z) with (⟨w, rfl⟩ | hne)
    · simp only [preimage, mem_singleton_iff, exp_eq_exp_iff_exists_int, set_of_exists]
      exact countable_Union fun m => countable_singleton _
    · push_neg  at hne
      simp [preimage, hne]
#align complex.countable_preimage_exp Complex.countable_preimage_exp

alias countable_preimage_exp ↔ _ _root_.set.countable.preimage_cexp
#align set.countable.preimage_cexp Set.Countable.preimage_cexp

theorem tendsto_log_nhdsWithin_im_neg_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0)
    (him : z.im = 0) : Tendsto log (𝓝[{ z : ℂ | z.im < 0 }] z) (𝓝 <| Real.log (abs z) - π * I) :=
  by
  have :=
    (continuous_of_real.continuous_at.comp_continuous_within_at
            (continuous_abs.continuous_within_at.log _)).Tendsto.add
      (((continuous_of_real.tendsto _).comp <|
            tendsto_arg_nhds_within_im_neg_of_re_neg_of_im_zero hre him).mul
        tendsto_const_nhds)
  convert this
  · simp [sub_eq_add_neg]
  · lift z to ℝ using him
    simpa using hre.ne
#align complex.tendsto_log_nhds_within_im_neg_of_re_neg_of_im_zero Complex.tendsto_log_nhdsWithin_im_neg_of_re_neg_of_im_zero

theorem continuousWithinAt_log_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0) (him : z.im = 0) :
    ContinuousWithinAt log { z : ℂ | 0 ≤ z.im } z :=
  by
  have :=
    (continuous_of_real.continuous_at.comp_continuous_within_at
            (continuous_abs.continuous_within_at.log _)).Tendsto.add
      ((continuous_of_real.continuous_at.comp_continuous_within_at <|
            continuous_within_at_arg_of_re_neg_of_im_zero hre him).mul
        tendsto_const_nhds)
  convert this
  · lift z to ℝ using him
    simpa using hre.ne
#align complex.continuous_within_at_log_of_re_neg_of_im_zero Complex.continuousWithinAt_log_of_re_neg_of_im_zero

theorem tendsto_log_nhdsWithin_im_nonneg_of_re_neg_of_im_zero {z : ℂ} (hre : z.re < 0)
    (him : z.im = 0) : Tendsto log (𝓝[{ z : ℂ | 0 ≤ z.im }] z) (𝓝 <| Real.log (abs z) + π * I) := by
  simpa only [log, arg_eq_pi_iff.2 ⟨hre, him⟩] using
    (continuous_within_at_log_of_re_neg_of_im_zero hre him).Tendsto
#align complex.tendsto_log_nhds_within_im_nonneg_of_re_neg_of_im_zero Complex.tendsto_log_nhdsWithin_im_nonneg_of_re_neg_of_im_zero

@[simp]
theorem map_exp_comap_re_atBot : map exp (comap re atBot) = 𝓝[≠] 0 := by
  rw [← comap_exp_nhds_zero, map_comap, range_exp, nhdsWithin]
#align complex.map_exp_comap_re_at_bot Complex.map_exp_comap_re_atBot

@[simp]
theorem map_exp_comap_re_atTop : map exp (comap re atTop) = comap abs atTop :=
  by
  rw [← comap_exp_comap_abs_at_top, map_comap, range_exp, inf_eq_left, le_principal_iff]
  exact eventually_ne_of_tendsto_norm_atTop tendsto_comap 0
#align complex.map_exp_comap_re_at_top Complex.map_exp_comap_re_atTop

end Complex

section LogDeriv

open Complex Filter

open Topology

variable {α : Type _}

theorem continuousAt_clog {x : ℂ} (h : 0 < x.re ∨ x.im ≠ 0) : ContinuousAt log x :=
  by
  refine' ContinuousAt.add _ _
  · refine' continuous_of_real.continuous_at.comp _
    refine' (Real.continuousAt_log _).comp complex.continuous_abs.continuous_at
    rw [complex.abs.ne_zero_iff]
    rintro rfl
    simpa using h
  · have h_cont_mul : Continuous fun x : ℂ => x * I := continuous_id'.mul continuous_const
    refine' h_cont_mul.continuous_at.comp (continuous_of_real.continuous_at.comp _)
    exact continuous_at_arg h
#align continuous_at_clog continuousAt_clog

theorem Filter.Tendsto.clog {l : Filter α} {f : α → ℂ} {x : ℂ} (h : Tendsto f l (𝓝 x))
    (hx : 0 < x.re ∨ x.im ≠ 0) : Tendsto (fun t => log (f t)) l (𝓝 <| log x) :=
  (continuousAt_clog hx).Tendsto.comp h
#align filter.tendsto.clog Filter.Tendsto.clog

variable [TopologicalSpace α]

theorem ContinuousAt.clog {f : α → ℂ} {x : α} (h₁ : ContinuousAt f x)
    (h₂ : 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousAt (fun t => log (f t)) x :=
  h₁.clog h₂
#align continuous_at.clog ContinuousAt.clog

theorem ContinuousWithinAt.clog {f : α → ℂ} {s : Set α} {x : α} (h₁ : ContinuousWithinAt f s x)
    (h₂ : 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousWithinAt (fun t => log (f t)) s x :=
  h₁.clog h₂
#align continuous_within_at.clog ContinuousWithinAt.clog

theorem ContinuousOn.clog {f : α → ℂ} {s : Set α} (h₁ : ContinuousOn f s)
    (h₂ : ∀ x ∈ s, 0 < (f x).re ∨ (f x).im ≠ 0) : ContinuousOn (fun t => log (f t)) s := fun x hx =>
  (h₁ x hx).clog (h₂ x hx)
#align continuous_on.clog ContinuousOn.clog

theorem Continuous.clog {f : α → ℂ} (h₁ : Continuous f) (h₂ : ∀ x, 0 < (f x).re ∨ (f x).im ≠ 0) :
    Continuous fun t => log (f t) :=
  continuous_iff_continuousAt.2 fun x => h₁.ContinuousAt.clog (h₂ x)
#align continuous.clog Continuous.clog

end LogDeriv


import data.nat.prime
import data.real.basic
-- import data.nat.gcd

open nat

-- 1º ejemplo
-- ==========

-- 1ª demostración
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
⟨5/2, by norm_num, by norm_num⟩

-- 2ª demostración
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
begin
  use 5 / 2,
  split; norm_num
end

-- 2º ejemplo
-- ==========

-- 1ª demostración
example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
begin
  rintros ⟨z, xltz, zlty⟩,
  exact lt_trans xltz zlty
end

-- 2ª demostración
example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
λ ⟨z, xltz, zlty⟩, lt_trans xltz zlty

-- 3º ejemplo
-- ==========

example : ∃ m n : ℕ,
  4 < m ∧ m < n ∧ n < 10 ∧ prime m ∧ prime n :=
begin
  use [5, 7],
  norm_num,
end

-- 4º ejemplo
-- ==========

example {x y : ℝ} : x ≤ y ∧ x ≠ y → x ≤ y ∧ ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩,
  use [h₀, λ h', h₁ (le_antisymm h₀ h')]
end

-- 2ª demostración
example {x y : ℝ} : x ≤ y ∧ x ≠ y → x ≤ y ∧ ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩,
  split,
    exact h₀,
  contrapose ! h₁,
  apply le_antisymm h₀ h₁
end


import tactic
import analysis.special_functions.trigonometric.arctan

.

namespace new

-- def complex : Type := ℝ × ℝ

structure complex : Type :=
(r : ℝ)
(im : ℝ)

structure polar : Type :=
(angle : ℝ)
(magnitude : ℝ)
-- (mag_nn : magnitude ≥ 0)

def i : complex := {r := 0, im := 1}

def add (a b : complex) : complex :=
{ r := a.r + b.r,
  im := a.im + b.im }

-- (a + b*i) * (c + d*i)
-- real part: ac - bd

def mul (a b : complex) : complex :=
{ r := a.r * b.r - a.im * b.im,
  im := a.r * b.im + a.im * b.r }

def neg (a : complex) : complex :=
{ r := -a.r, im := -a.im }

-- more fields to fill in here
instance : comm_ring complex :=
{ add := add,
  neg := neg,
  mul := mul,
  add_assoc := begin
    intros a b c,
    simp [add],
    apply and.intro;
    ring
  end,
  zero := {r := 0, im := 0},
  zero_add := begin intro a, simp [add], cases a, refl end   }

example (a b c : complex) : a*b + c = c + b*a - a + a :=
by ring

lemma mul_def (a b : complex) : a * b =
  { r := a.r * b.r - a.im * b.im,
    im := a.r * b.im + a.im * b.r } :=
rfl

noncomputable def to_polar_rep (a : complex) : polar :=
{ angle := real.arctan (a.im / a.r),
  magnitude := real.sqrt (a.r^2 + a.im^2)  }

noncomputable def of_polar_rep (p : polar) : complex :=
{ r := p.magnitude * real.cos p.angle,
  im := p.magnitude * real.sin p.angle }

theorem to_and_from (p : polar)
  (hm : p.magnitude > 0)
  (ha1 : -(real.pi / 2) < p.angle)
  (ha2 : p.angle < real.pi / 2)
  :
  to_polar_rep (of_polar_rep p) = p :=
begin
  cases p,
  simp [to_polar_rep, of_polar_rep],
  apply and.intro,
  { rw [mul_div_mul_left, ← real.tan_eq_sin_div_cos, real.arctan_tan],
    assumption,
    assumption,
    linarith },
  { suffices : (p_magnitude * real.cos p_angle) ^ 2 +
     (p_magnitude * real.sin p_angle) ^ 2 = p_magnitude^2,
     { rw [this, real.sqrt_sq], linarith },
      have : (real.sin p_angle) ^2 + (real.cos p_angle) ^ 2 = 1 :=
        real.sin_sq_add_cos_sq _,
      linear_combination p_magnitude ^ 2 * this }
end

instance : has_coe ℝ complex :=
 { coe := λ r, {r := r, im := 0} }

@[simp] lemma im_coe (r : ℝ) : (r : complex).im = 0 :=
rfl

@[simp] lemma re_coe (r : ℝ) : (r : complex).r = r :=
rfl

theorem to_polar_real (r : ℝ) (hr : r > 0) :
  (to_polar_rep r).angle = 0 :=
begin
  simp only [to_polar_rep, im_coe, zero_div, real.arctan_zero],
end

def conjugate (c : complex) : complex :=
{ r := c.r,
  im := -c.im }

def reals : set complex :=
{ c : complex | c.im = 0 }

lemma mul_conjugate_is_real (c : complex) :
  c * conjugate c ∈ reals :=
begin
  simp [reals, conjugate, mul_def],
  ring,
end

noncomputable def magnitude (c : complex) : ℝ :=
real.sqrt ((c * conjugate c).r)

variables s t : set complex
variable c : complex

#check c ∈ s
#check s c

#check s ∩ t
-- λ x, s x ∧ t x

#check set.mem_inter

#check s ∪ t
-- λ x, s x ∨ t x

end new


section propositional

variables P Q R : Prop

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

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

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  by_contradiction pboom,
  exact nnp pboom,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    exact doubleneg_elim P,
    exact doubleneg_intro P,
end

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

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

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

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intros npoq hp,
  cases npoq with np hq,
    contradiction,
    exact hq,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros poq np,
  cases poq with hp hq,
    contradiction,
    exact hq,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros piq nq hp,
  have hq: Q := piq hp,
  exact nq hq,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros nqinp hp,
  by_contradiction nq,
  have np: ¬P := nqinp nq,
  exact np hp,
end

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

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

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

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros piq_ip np,
  have piq: (P → Q),
    intro hp,
    contradiction,
  have p:P  := piq_ip piq,
  exact np p,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros poq npenq,
  cases poq with hp hq,
    cases npenq with np nq,
      exact np hp,
    cases npenq with np nq,
      exact nq hq,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intros peq nponq,
  cases nponq with np nq,
    cases peq with hp hq,
      exact np hp,
    cases peq with hp hq,
      exact nq hq,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_poq,
  split,
    intro hp,
      have poq: (P∨Q),
        left,
        exact hp,
      exact n_poq poq,
    intro hq,
      have poq: (P∨Q),
        right,
        exact hq,
      exact n_poq poq,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros npenq poq,
  cases poq with hp hq,
    cases npenq with np nq,
      exact np hp,
    cases npenq with np nq,
      exact nq hq,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_peq,
  by_contradiction nq_o_np,
  apply nq_o_np,
    right,
    intro hp,
  apply nq_o_np,
    left,
    intro hq,
  exact n_peq ⟨hp,hq⟩,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros nqonp peq,
  cases nqonp with nq np,
    cases peq with hp hq,
      exact nq hq,
    cases peq with hp hq,
      exact np hp,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
    exact demorgan_conj P Q,
    exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    exact demorgan_disj 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 pe_qor,
  cases pe_qor with hp qor,
    cases qor with hq hr,
      left,
      split,
        exact hp,
        exact hq,
      right,
      split,
        exact hp,
        exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro peq_o_per,
  split,
    cases peq_o_per with peq per,
      cases peq with hp hq,
        exact hp,
      cases per with hp hr,
        exact hp,
    cases peq_o_per with peq per,
      left,
        cases peq with hp hq,
          exact hq,
      right,
        cases per with hp hr,
          exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro po_qer,
  split,
    cases po_qer with hp qer,
      left,
        exact hp,
      cases qer with hq hr,
        right,
          exact hq,
    cases po_qer with hp qer,
      left,
        exact hp,
      cases qer with hq hr,
        right,
          exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro poq_e_por,
  cases poq_e_por with poq por,
    cases poq with hp hq,
      left,
        exact hp,
      cases por with hp hr,
        left,
          exact hp,
        right,
          split,
            exact hq,
            exact hr,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros peq_ir hp hq,
  exact peq_ir ⟨hp,hq⟩,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros pi_qir peq,
  cases peq with hp hq,
    exact pi_qir 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 peq,
  cases peq with hp hq,
    exact hp,
end

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

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
    exact weaken_conj_right P P,
    intro hp,
    split,
      exact hp,
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    intro pop,
    cases pop with hp hp,
      exact hp,
      exact hp,
    exact weaken_disj_right P P,
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
  intros n_eapa a hp,
  exact n_eapa ⟨a,hp⟩,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros a_npa eapa,
  cases eapa with a pa,
    exact a_npa a pa,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro n_apa,
  by_contradiction ea_npa,
  apply n_apa,
    intro a,
    by_contradiction npa,
  apply ea_npa,
    existsi a,
    intro pa,
  exact npa pa,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros ea_npa apa,
  cases ea_npa with a npa,
  have pa : P a := apa a,
  exact npa pa,
end

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

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

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros eapa a_npa,
  cases eapa with a pa,
  exact a_npa a pa,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros apa ea_npa,
  cases ea_npa with a npa,
  have pa : P a := apa a,
  exact npa pa,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros n_ea_npa a,
  by_contradiction np,
  exact n_ea_npa ⟨ a,np ⟩,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro n_a_npa,
  by_contradiction eapa,
  apply n_a_npa,
    intro a,
    intro pa,
  apply eapa,
    existsi a,
    exact pa,
end

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

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
    apply exists_as_neg_forall,
    apply exists_as_neg_forall_converse,
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 ea_paeqa,
  split,
    cases ea_paeqa  with a paeqa,
      existsi a,
      cases paeqa with pa qa,
        exact pa,
    cases ea_paeqa with a paeqa,
      existsi a,
      cases paeqa with pa qa,
        exact qa,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro ea_paoqa,
  cases ea_paoqa with a paoqa,
  cases paoqa with pa qa,
    left,
      existsi a,
      exact pa,
    right,
      existsi a,
      exact qa,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro eapa_o_eaqa,
  cases eapa_o_eaqa with eapa eaqa,
    cases eapa with a pa,
      existsi a,
      left,
        exact pa,
    cases eaqa with a qa,
      existsi a,
      right,
        exact qa,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro a_paeqa,
  split,
    intro a,
    have paeqa : P a ∧ Q a := a_paeqa a,
    cases paeqa with pa qa,
      exact pa,
    intro a,
    have paeqa : P a ∧ Q a := a_paeqa a,
    cases paeqa with pa qa,
      exact qa,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intros apa_e_aqa a,
  cases apa_e_aqa with apa aqa,
    split,
      exact apa a,
      exact aqa a,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros apa_o_aqa a,
  cases apa_o_aqa with apa aqa,
    left,
      exact apa a,
    right,
      exact aqa a,
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


-- Mathematical induction is the process of, by simple rules,
-- we achieve a generalization (the opposite of deduction).
-- Inductive data types follows the same principles, they
-- have a base case and a inductive case.

inductive ℕ : Type where
  | z : ℕ      -- Here "z" (zero) is the base case
  | s : ℕ → ℕ  -- And "s" (sucessor) is the inductive case

theorem seven : ℕ := ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s (ℕ.s ℕ.z))))))

-- Some cool definitions to make it easier to work with this new
-- data type

def fromDefaultNat : Nat → ℕ
  | 0   => ℕ.z
  | n+1 => ℕ.s (fromDefaultNat n)

def toDefaultNat : ℕ → Nat
  | ℕ.z   => 0
  | ℕ.s n => Nat.succ (toDefaultNat n)

instance : OfNat ℕ n where ofNat := fromDefaultNat n
instance : Repr ℕ where reprPrec x _ := repr (toDefaultNat x)

-- Operations are defined recursively like that

def add : ℕ → ℕ → ℕ
  | ℕ.z,   m => m
  | ℕ.s n, m => ℕ.s (add n m)

def add' : ℕ → ℕ → ℕ
  | m, ℕ.z   => m
  | m, ℕ.s n => ℕ.s (add' m n)

def mult : ℕ → ℕ → ℕ
  | ℕ.z,   m    => ℕ.z
  | ℕ.s ℕ.z,  m => m
  | ℕ.s n, m    => add m (mult n m)

-- Like subtraction but trim at zero

def monus : ℕ → ℕ → ℕ
  | ℕ.z,       m => ℕ.z
  | m,       ℕ.z => m
  | ℕ.s m, ℕ.s n => monus m n

infixl:65   " + " => add
infixl:66   " * " => mult

instance : HSub ℕ ℕ ℕ where hSub := monus

example : 3 * 3 + 5 - 4 = 10 := rfl

-- Binary natural numbers

inductive 𝔹 : Type where
  | nob  : 𝔹
  | zero : 𝔹 → 𝔹
  | one  : 𝔹 → 𝔹
  deriving Repr

def Bin.inc : 𝔹 → 𝔹
  | 𝔹.nob    => 𝔹.one 𝔹.nob
  | 𝔹.zero n => 𝔹.one n
  | 𝔹.one  n => 𝔹.zero (inc n)

def Bin.toNat : 𝔹 → ℕ
  | 𝔹.nob    => ℕ.z
  | 𝔹.zero n => toNat n * 2
  | 𝔹.one  n => toNat n * 2 + 1

def Nat.toBin : ℕ → 𝔹
  | ℕ.z   => 𝔹.nob
  | ℕ.s n => Bin.inc (toBin n)

instance : OfNat 𝔹 n where ofNat := Nat.toBin (fromDefaultNat n)

-- Proofs

theorem Nt.Add.sucAssoc : ∀ (m n : ℕ), ℕ.s m + n = ℕ.s (m + n)
  | ℕ.z, n   => rfl
  | ℕ.s m, n => congrArg ℕ.s (sucAssoc m n)

theorem Nt.Add.sucShift : ∀ (m n : ℕ), ℕ.s m + n = m + ℕ.s n
  | ℕ.z, n => rfl
  | ℕ.s m, n => Eq.trans (Nt.Add.sucAssoc (ℕ.s m) n) (congrArg ℕ.s (sucShift m n))

theorem Nt.Add.assoc : ∀ (m n p : ℕ), (m + n) + p = m + (n + p)
  | ℕ.z  , n, p => rfl
  | ℕ.s m, n, p => by
    rw    [Nt.Add.sucAssoc]
    apply congrArg ℕ.s (assoc m n p)

theorem Nt.Add.rightId : ∀ (m : ℕ), m + 0 = m
  | ℕ.z   => rfl
  | ℕ.s n => congrArg ℕ.s (rightId n)

theorem Nt.Add.comm : ∀ (m n : ℕ), m + n = n + m
  | 0,   n => Eq.symm (Nt.Add.rightId n)
  | ℕ.s m, n => by
    rw [Nt.Add.sucAssoc m n, comm m n, ← Nt.Add.sucShift n m]
    rfl

theorem Nt.Mul.leftId : ∀ (m : ℕ), ℕ.s 0 * m = m
  | 0 => rfl
  | ℕ.s m => rfl

theorem Nt.Mul.leftNeutral : ∀ (m : ℕ), 0 * m = 0
  | 0     => rfl
  | ℕ.s m => rfl

theorem Nt.Mul.sucLeft : ∀ (m n : ℕ), ℕ.s m * n = n + (m * n)
  | 0, n     => by apply (Eq.symm (Nt.Add.rightId n))
  | ℕ.s m, n => by rfl

theorem Nt.Mul.sucRight : ∀ (m n : ℕ), m * ℕ.s n = (m * n) + m
  | 0, n     => by simp; rfl
  | ℕ.s m, n => by rw [Nt.Mul.sucLeft m, Nt.Mul.sucLeft, sucRight m n, Nt.Add.assoc, Nt.Add.sucShift, ←Nt.Add.sucAssoc, Nt.Add.sucShift];

theorem Nt.Mul.distribAdd : ∀ (m n p : ℕ), (m + n) * p = m * p + n * p
  | 0  , n, p   => by rfl
  | ℕ.s m, n, p => by
    rw [Nt.Add.sucAssoc]
    simp [Nt.Mul.sucLeft]
    rw [distribAdd m n p, Nt.Add.assoc]

theorem Nt.Mul.rightNeutral : ∀ (m : ℕ), m * 0 = 0
  | 0     => rfl
  | ℕ.s m => by rw[Nt.Mul.sucLeft, rightNeutral m]; rfl

theorem Nt.Mul.rightId : ∀ (m : ℕ), m * ℕ.s 0 = m
  | 0     => rfl
  | ℕ.s m => by rw[Nt.Mul.sucLeft, rightId m]; rfl

theorem Nt.Mul.comm :  ∀ (m n : ℕ), m * n = n * m
  | 0  ,   n => Eq.symm (Nt.Mul.rightNeutral n)
  | ℕ.s m, n => by rw [Nt.Mul.sucRight, Nt.Mul.sucLeft, Nt.Add.comm, comm m n]

termination_by
  Nt.Mul.comm m _ => sizeOf m

-- Binary

theorem Bin.incEqNatSuc : ∀ (b : 𝔹), Bin.toNat (Bin.inc b) = ℕ.s (Bin.toNat b)
  | 𝔹.nob => rfl
  | 𝔹.zero n => by simp [Bin.toNat]; rw [Nt.Add.comm]; rfl
  | 𝔹.one n => by
    simp [Bin.toNat]
    rw [Nt.Add.comm, incEqNatSuc n, Nt.Mul.sucLeft]
    rfl

termination_by
  Bin.incEqNatSuc t => sizeOf t

theorem Bin.eqNat : ∀ (n : ℕ), Bin.toNat (Nat.toBin n) = n
  | 0     => rfl
  | ℕ.s n => by simp [Nat.toBin]; rw [Bin.incEqNatSuc, eqNat n];

termination_by
  Bin.eqNat t => sizeOf t


import linear_algebra.basic
import linear_algebra.basis
open linear_map
open is_basis
universe variables u v w
variables {R : Type v}  [comm_ring R]
          {X : Type w} [fintype X] [decidable_eq X]

open_locale big_operators
/--
    change a map by zero outside a `finset s`.
-/
def updapte_with_zero ( φ : X → R) (s : finset X) : X → R := λ x, if x ∈ s then φ x else 0
/--
   `(updapte_with_zero φ s) x • B x = if x ∈ s then  φ x • B x else 0`
-/
lemma updapte_with_zero_smul ( φ : X → R) (s : finset X) (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(λ x : X, (updapte_with_zero φ s) x • B x) = (λ x : X, if x ∈ s then  φ x • B x else 0) :=
begin
    funext, unfold updapte_with_zero,split_ifs, exact rfl,
    rw zero_smul,
end
/--
    Helper for finite linear_independance
    if `∀ φ : X → R,  (∑  x, φ x • B x = 0) → ∀ x : X, φ x = 0 )` then  `linear_independent R B`
-/
variables ( φ : X → R)
lemma linear_independent_finite (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(∀ φ : X → R, (∑  x, φ x • B x = 0) → ∀ x : X, φ x = 0 )  →  linear_independent R B :=
begin
    intros hyp_ind,
    rw linear_independent_iff',
    intros s φ hyp x hyp_x,
    --let F := hyp_ind φ,
    specialize hyp_ind (updapte_with_zero φ s), rw updapte_with_zero_smul at hyp_ind,
    have R : ∑ x, ite (x ∈ s) (φ x • B x) 0 = ∑ i in  s, φ i • B i,
        rw finset.sum_ite, rw finset.sum_const_zero, rw add_zero,
        congr, exact finset.univ_inter s,
    rw hyp at R,
    specialize hyp_ind R x,rw ← hyp_ind,
    unfold updapte_with_zero,
    split_ifs,exact rfl,
end
/--
    Helper for finite span
    if `∀ m :  M, ∃ φ : X → R, m =  ∑  x , (φ x) • (B x)` then
     `submodule.span R (set.range B) = ⊤`
-/
lemma gen_finite (M : Type u)[add_comm_group M] [ module R M] (B : X → M) :
(∀ m :  M, ∃ φ : X → R, m =  ∑  x , (φ x) • (B x)) → submodule.span R (set.range B) = ⊤ :=
begin
    intros hyp,rw eq_top_iff, rw submodule.le_def',
    intros m, intros,
    specialize hyp m,
    rcases hyp with ⟨φ , hyp_phi ⟩,
    rw hyp_phi,
    let p := submodule.span R (set.range B),
    apply submodule.sum_mem p ,
    intros x, intros,
    apply submodule.smul_mem p, apply submodule.subset_span, apply set.mem_range_self,
end


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

import analysis.special_functions.integrals
open interval_integral real
open_locale real

/-! ### Simple functions -/

/- constants -/
example : ∫ x : ℝ in 8..11, (1 : ℝ) = 3 := by norm_num
example : ∫ x : ℝ in 5..19, (12 : ℝ) = 168 := by norm_num

/- the identity function -/
example : ∫ x : ℝ in (-1)..4, x = 15 / 2 := by norm_num
example : ∫ x : ℝ in 4..5, x * 2 = 9 := by norm_num

/- inverse -/
example : ∫ x : ℝ in 2..3, x⁻¹ = log (3 / 2) := by norm_num

/- natural powers -/
example : ∫ x : ℝ in 2..4, x ^ (3 : ℕ) = 60 := by norm_num

/- trigonometric functions -/
example : ∫ x in 0..π, sin x = 2 := by norm_num
example : ∫ x in 0..π/4, cos x = sqrt 2 / 2 := by simp
example : ∫ x in 0..π, 2 * sin x = 4 := by norm_num
example : ∫ x in 0..π/2, cos x / 2 = 1 / 2 := by simp
example : ∫ x : ℝ in 0..1, 1 / (1 + x ^ 2) = π / 4 := by simp
example : ∫ x in 0..2*π, sin x ^ 2 = π := by simp [mul_div_cancel_left]
example : ∫ x in 0..π/2, cos x ^ 2 / 2 = π / 8 := by norm_num [div_div_eq_div_mul]
example : ∫ x in 0..π, cos x ^ 2 - sin x ^ 2 = 0 := by simp [integral_cos_sq_sub_sin_sq]
example : ∫ x in 0..π/2, sin x ^ 3 = 2 / 3 := by norm_num
example : ∫ x in 0..π/2, cos x ^ 3 = 2 / 3 := by norm_num
example : ∫ x in 0..π, sin x * cos x = 0 := by simp
example : ∫ x in 0..π, sin x ^ 2 * cos x ^ 2 = π / 8 := by simpa using sin_nat_mul_pi 4

/- the exponential function -/
example : ∫ x in 0..2, -exp x = 1 - exp 2 := by simp

/- the logarithmic function -/
example : ∫ x in 1..2, log x = 2 * log 2 - 1 := by { norm_num, ring }

/- linear combinations (e.g. polynomials) -/
example : ∫ x : ℝ in 0..2, 6*x^5 + 3*x^4 + x^3 - 2*x^2 + x - 7 = 1048 / 15 := by norm_num
example : ∫ x : ℝ in 0..1, exp x + 9 * x^8 + x^3 - x/2 + (1 + x^2)⁻¹ = exp 1 + π / 4 := by norm_num

/-! ### Functions composed with multiplication by and/or addition of a constant -/

/- many examples are computable by `norm_num` -/
example : ∫ x in 0..2, -exp (-x) = exp (-2) - 1 := by norm_num
example : ∫ x in 1..2, exp (5*x - 5) = 1/5 * (exp 5 - 1) := by norm_num
example : ∫ x in 0..π, cos (x/2) = 2 := by norm_num
example : ∫ x in 0..π/4, sin (2*x) = 1/2 := by norm_num [mul_div_comm, mul_one_div]
example (ω φ : ℝ) : ω * ∫ θ in 0..π, sin (ω*θ + φ) = cos φ - cos (ω*π + φ) := by simp

/- some examples may require a bit of algebraic massaging -/
example {L : ℝ} (h : L ≠ 0) : ∫ x in 0..2/L*π, sin (L/2 * x) = 4 / L :=
begin
  norm_num [div_ne_zero h, ← mul_assoc],
  field_simp [h, mul_div_cancel],
  norm_num,
end

/- you may need to provide `norm_num` with the composition lemma you are invoking if it has a
  difficult time recognizing the function you are trying to integrate -/
example : ∫ x : ℝ in 0..2, 3 * (x + 1) ^ 2 = 26 :=
  by norm_num [integral_comp_add_right (λ x, x ^ 2)]
example : ∫ x : ℝ in -1..0, (1 + (x + 1) ^ 2)⁻¹ = π / 4 :=
  by simp [integral_comp_add_right (λ x, (1 + x ^ 2)⁻¹)]

/-! ### Compositions of functions (aka "change of variables" or "integration by substitution") -/

/- `interval_integral.integral_comp_mul_deriv` can be used to simplify integrals of the form
  `∫ x in a..b, (g ∘ f) x * f' x`, where `f'` is the derivative of `f`, to `∫ x in f a..f b, g x` -/
example {a b : ℝ} : ∫ x in a..b, exp (exp x) * exp x = ∫ x in exp a..exp b, exp x :=
integral_comp_mul_deriv (λ x hx, has_deriv_at_exp x) continuous_on_exp continuous_exp

/- if it is known (to mathlib), the integral of `g` can then be evaluated using `simp`/`norm_num` -/
example : ∫ x in 0..1, exp (exp x) * exp x = exp (exp 1) - exp 1 :=
by rw integral_comp_mul_deriv (λ x hx, has_deriv_at_exp x) continuous_on_exp continuous_exp; simp

/- a more detailed example -/
example : ∫ x in 0..2, exp (x ^ 2) * (2 * x) = exp 4 - 1 :=
begin                                                    -- let g := exp x, f := x ^ 2, f' := 2 * x
  rw integral_comp_mul_deriv (λ x hx, _),                -- simplify to ∫ x in f 0..f 2, g x
  { norm_num },                                          -- compute the integral
  { exact continuous_on_const.mul continuous_on_id },    -- show that f' is continuous on [0, 2]
  { exact continuous_exp },                              -- show that g is continuous
  { simpa using has_deriv_at_pow 2 x },                  -- show that f' = derivative of f on [0, 2]
end

/- alternatively, `interval_integral.integral_deriv_comp_mul_deriv` can be used to compute integrals
  of this same form, provided that you also know that `g` is the derivative of some function -/
example : ∫ x : ℝ in 0..1, exp (x ^ 2) * (2 * x) = exp 1 - 1 :=
begin
  rw integral_deriv_comp_mul_deriv (λ x hx, _) (λ x hx, has_deriv_at_exp (x^2)) _ continuous_exp,
  { simp },
  { simpa using has_deriv_at_pow 2 x },
  { exact continuous_on_const.mul continuous_on_id },
end


import Mathlib.Tactic.Basic
/-!
## Reasoning about Equality

Equality (`=`) is also a basic logical constant. It is characterized by the following
introduction and elimination rules:

```lean
Eq.refl : ∀a, a = a
Eq.symm : ?a = ?b → ?b = ?a
Eq.trans : ?a = ?b → ?b = ?c → ?a = ?c
Eq.subst : ?a = ?b → ?p ?a → ?p ?b
```

The first three lemmas are introduction rules specifying that `=` is an equivalence
relation. The fourth lemma is an elimination rule that allows us to replace equals
for equals in an arbitrary context, represented by the higher-order variable `?p`.

An example will show how this works. Below, we apply `Eq.subst` to rewrite
`f a b` to `f a' b`, using the hypothesis `a = a'`:
-/

theorem cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') : f a b = f a' b := by
  apply Eq.subst ha (motive := λ x => f a b = f x b)
  apply Eq.refl

-- BUGBUG why does lean4 require this motive, lean3 did not
-- See https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/debugging.20'apply'.20failed.20to.20unify

/-!
The `Eq.subst` instance we use has `?a := a`, `?b := a'`, and `?p := (λ x => f a b = f x b)`:
```lean
a = a' → (λx => f a b = f x b) a → (λx => f a b = f x b) a'
```
In β-reduced form:
```lean
a = a' → f a b = f a b → f a b = f a' b
```
The lemma's first assumption matches the hypothesis `ha : a = a'`, which is passed as an argument in the
first apply invocation. The lemma's second assumption is a trivial equality that can be proved by
apply `Eq.refl` or `rfl`. The lemma's conclusion matches the goal’s target. Notice how a higher-order
variable (e.g., `?p`) can represent an arbitrary context (e.g., `f . . . b`) around a term (e.g.,
`a` or `a'`). This works because `apply` unifies up to computation, including β-conversion.

The `Eq.subst` lemma can be applied several times in sequence, as follows:

(λx => f x n₁ ... nₖ) a = f a` n₁ ... nₖ

-/

lemma cong_two_args {α : Type} (a a' b b' : α)
  (f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' := by
  apply Eq.subst ha (motive := λ x => f a b = f x b')
  apply Eq.subst hb (motive := λ x => f a b = f a x)
  apply Eq.refl
  -- rw [ha, hb]

-- BUGBUG lean3 did not require these explicit motives...

/-!
Since rewriting in this way is such a common operation, Lean provides a `rw` tactic
to achieve the same result. The tactic will also notice if `rfl` is applicable:
-/
lemma cong_two_args₂ {α : Type} (a a' b b' : α)
  (f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' := by
  rw [ha]
  rw [hb]

/-!
A note on parsing: Equality binds more tightly than the logical connectives.
Thus, `a = a' ∧ b = b'` should be read as (a = a') ∧ (b = b').

-/


/-
Notes for 11-07-2019.lean
-/

namespace test
-- The and'' proposition is really just like a product inductive type
inductive and'' (α β : Prop) : Prop
| intro : α → β → and''

-- but over the universe of Prop rather than of Type

inductive prod' (α β : Type) : Type
| pair : α → β → prod'

structure and (a b : Prop) : Prop :=
    intro :: (left : a) (right : b)

def and.elim_left {α β : Prop} : and α β → α
| (and.intro a b) := a

def and.elim_right {α β : Prop} : and α β → β
| (and.intro a b) := b

end test

def P1 : Prop := 0 = 0
def P2 : Prop := 1 = 1
def p1 : P1 := eq.refl 0

def P1_and_P2 := P1 ∧ P2

theorem pf : P1_and_P2 :=
begin
    apply and.intro,
    apply eq.refl,
    apply eq.refl
end

inductive Person
| harsh
| joe
| will

inductive friends : Person → Person → Prop
| refl : ∀ (a : Person), friends a a
| symm : ∀ (a b : Person), (friends a b) → (friends b a)
| trans : ∀ (a b c : Person), (friends a b) → (friends b c) → (friends a c)

theorem joe_is_friends_with_self : friends Person.joe Person.joe :=
begin
    apply friends.refl
end

open Person
theorem if_harsh_then_harsh : (friends harsh joe) → (friends joe harsh) :=
begin
    apply friends.symm,
end

def transitive' {α : Type} (r : α → α → Prop) : Prop := ∀ (a b c : α), r a b → r b c → r a c

theorem friends_transitive : transitive' friends :=
begin
    unfold transitive',
    assume (a b c),
    assume ab bc,
    exact friends.trans a b c ab bc,
end

/-
Prove 0=0.

Proof: Equality is reflexive. this means that for any value a of any type α, a = a.

∀ {T : Type}, t → t = t

We apply this axiom to 0 in particular, to derive a proof that 0=0.
-/


import data.fintype data.finset algebra.group_power
open fintype finset
/- 6. Say A and B are finite sets, with sizes a and b respectively. Prove that the set B^A of functions A → B has size b^a. What about the case a = b = 0?
-/

#check finset.card
variables {A B C : Type} [fintype A] [decidable_eq A] [fintype B]
variables {a b : ℕ }
theorem Q1006 (hp: card A = a) (hq : card B = b) : card (A → B) = b^a := sorry


def isEven : ℕ → Prop := λ n, ∃ m : ℕ, m * 2 = n

lemma eightIsEven : isEven 8 :=
begin
    unfold isEven,
    exact ⟨4, dec_trivial⟩,
end

-- def isNonZ : ℕ → Prop := λ n, n ≠ 0

def isnonz : Prop := ∃ n : ℕ, n ≠ 0

lemma isNonZ : isnonz := ⟨1, dec_trivial⟩

lemma isNonZ' : isnonz :=
begin
    apply exists.intro 1,
    assume : 1 = 0,
    show false, from nat.no_confusion this,
end

example : ∀ {P S : ℕ → Prop},
    (∃ n : ℕ, P n ∧ S n) → (∃ n : ℕ, S n ∧ P n) :=
begin
    assume P S,
    assume ex,
    apply exists.elim ex,
        assume x,
        assume px_sx,
        apply exists.intro x,
            apply and.comm.mpr,
            assumption,
end

example : ∀ {P S : ℕ → Prop},
    (∃ n : ℕ, P n ∧ S n) → (∃ n : ℕ, S n ∧ P n) :=
begin
    assume P S,
    assume ex,
    apply exists.elim ex,
        assume x,
        assume px_sx,
        apply exists.intro x,
            apply and.intro,
                exact px_sx.2,
                exact px_sx.1,
end

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

example : isSquare 9 := ⟨81, dec_trivial⟩

example : isSquare 9 :=
begin
    apply exists.intro 81,
    apply rfl
end


-- Suma de no negativos (en Lean)
-- ==============================

-- Demostrar si a y b son números reales no negativos,
-- entonces a + b es no negativo.

import data.real.basic

variables (a b : ℝ)

-- 1ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
begin
  calc 0 ≤ a     : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
end

-- 2ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
add_nonneg ha hb

-- 3ª demostración
example
  (ha : 0 ≤ a)
  (hb : 0 ≤ b)
  : 0 ≤ a + b :=
by linarith


-- Monotonia_de_la_multiplicacion_por_no_positivo.lean
-- Monotonía de la multiplicación por no positivo.
-- José A. Alonso Jiménez
-- Sevilla, 22 de agosto de 2020
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si a, b y c son números reales tales que
-- c ≤ 0 y a ≤ b, entonces b*c ≤ a*c.
-- ----------------------------------------------------------------------

import data.real.basic

variables {a b c : ℝ}

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

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  rw ← sub_nonneg,
  have h : a * c - b * c = (a - b) * c,
  { ring },
  { rw h,
    apply mul_nonneg_of_nonpos_of_nonpos,
    { rwa sub_nonpos, },
    { exact hc, }},
end

-- Comentario: Se ha usado los lemas
-- + mul_nonneg_of_nonpos_of_nonpos : a ≤ 0 → b ≤ 0 → 0 ≤ a * b
-- + sub_nonpos : a - b ≤ 0 ↔ a ≤ b

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

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  have hab' : a - b ≤ 0,
  { rwa ← sub_nonpos at hab, },
  have h1 : 0 ≤ (a - b) * c,
  { exact mul_nonneg_of_nonpos_of_nonpos hab' hc, },
  have h2 : (a - b) * c = a * c - b * c,
  { ring, },
  have h3 : 0 ≤ a * c - b * c,
  { rwa h2 at h1, },
  rwa sub_nonneg at h3,
end

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

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
begin
  rw ← sub_nonneg,
  have hab' : a - b ≤ 0,
  { rwa sub_nonpos, },
  calc 0 ≤ (a - b)*c  : mul_nonneg_of_nonpos_of_nonpos hab' hc
     ... =  a*c - b*c : by ring,
end

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

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
mul_mono_nonpos hc hab

-- Comentario: Se usa el lema
-- + mul_mono_nonpos : 0 ≥ c → b ≤ a → a * c ≤ b * c

-- 5ª demostración
-- ===============

example
  (hc : c ≤ 0)
  (hab : a ≤ b)
  : b * c ≤ a * c :=
by nlinarith


/-
Copyright (c) 2021 Matt Kempster. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Matt Kempster
-/
import geometry.euclidean.triangle

/-!
# Freek № 57: Heron's Formula

This file proves Theorem 57 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/),
also known as Heron's formula, which gives the area of a triangle based only on its three sides'
lengths.

## References

* https://en.wikipedia.org/wiki/Herons_formula

-/

open real euclidean_geometry
open_locale real euclidean_geometry

local notation `√` := real.sqrt

variables {V : Type*} {P : Type*} [inner_product_space ℝ V] [metric_space P]
  [normed_add_torsor V P]

include V

/-- **Heron's formula**: The area of a triangle with side lengths `a`, `b`, and `c` is
  `√(s * (s - a) * (s - b) * (s - c))` where `s = (a + b + c) / 2` is the semiperimeter.
  We show this by equating this formula to `a * b * sin γ`, where `γ` is the angle opposite
  the side `c`.
 -/
theorem heron {p1 p2 p3 : P} (h1 : p1 ≠ p2) (h2 : p3 ≠ p2) :
  let a := dist p1 p2, b := dist p3 p2, c := dist p1 p3, s := (a + b + c) / 2 in
  1/2 * a * b * sin (∠ p1 p2 p3) = √(s * (s - a) * (s - b) * (s - c)) :=
begin
  intros a b c s,
  let γ := ∠ p1 p2 p3,
  obtain := ⟨(dist_pos.mpr h1).ne', (dist_pos.mpr h2).ne'⟩,
  have cos_rule : cos γ = (a * a + b * b - c * c) / (2 * a * b) := by field_simp [mul_comm, a,
    dist_sq_eq_dist_sq_add_dist_sq_sub_two_mul_dist_mul_dist_mul_cos_angle p1 p2 p3],
  let numerator := (2*a*b)^2 - (a*a + b*b - c*c)^2,
  let denominator := (2*a*b)^2,
  have split_to_frac : 1 - cos γ ^ 2 = numerator / denominator := by field_simp [cos_rule],
  have numerator_nonneg : 0 ≤ numerator,
  { have frac_nonneg: 0 ≤ numerator / denominator := by linarith [split_to_frac, cos_sq_le_one γ],
    cases div_nonneg_iff.mp frac_nonneg,
    { exact h.left },
    { simpa [h1, h2] using le_antisymm h.right (sq_nonneg _) } },
  have ab2_nonneg : 0 ≤ (2 * a * b) := by norm_num [mul_nonneg, dist_nonneg],
  calc  1/2 * a * b * sin γ
      = 1/2 * a * b * (√numerator / √denominator) : by rw [sin_eq_sqrt_one_sub_cos_sq,
                                                          split_to_frac, sqrt_div numerator_nonneg];
                                                      simp [angle_nonneg, angle_le_pi]
  ... = 1/4 * √((2*a*b)^2 - (a*a + b*b - c*c)^2)  : by { field_simp [ab2_nonneg], ring }
  ... = 1/4 * √(s * (s-a) * (s-b) * (s-c) * 4^2)  : by { simp only [s], ring_nf }
  ... = √(s * (s-a) * (s-b) * (s-c))              : by rw [sqrt_mul', sqrt_sq, div_mul_eq_mul_div,
                                                          one_mul, mul_div_cancel];
                                                      norm_num,
end