/-

# Proposition world.

A Proposition is a true/false statement, like `2 + 2 = 4` or `2 + 2 = 5`.
Just like we can have concrete sets in Lean like `mynat`, and abstract
sets called things like `X`, we can also have concrete propositions like
`2 + 2 = 5` and abstract propositions called things like `P`.

Mathematicians are very good at conflating a theorem with its proof.
They might say "now use theorem 12 and we're done". What they really
mean is "now use the proof of theorem 12..." (i.e. the fact that we proved
it already). Particularly problematic is the fact that mathematicians
use the word Proposition to mean "a relatively straightforward statement
which is true" and computer scientists use it to mean "a statement of
arbitrary complexity, which might be true or false". Computer scientists
are far more careful about distinguishing between a proposition and a proof.
For example: `x + 0 = x` is a proposition, and `add_zero x`
is its proof. The convention we'll use is capital letters for propositions
and small letters for proofs.

In this world you will see the local context in the following kind of state:

```
P : Prop
p : P
```

Here `P` is the true/false statement (the statement of proposition), and `p` is its proof.
It's like `P` being the set and `p` being the element. In fact computer scientists
sometimes think about the following analogy: propositions are like sets,
and their proofs are like their elements.

## What's going on in this world?

We're going to learn about manipulating propositions and proofs.
Fortunately, we don't need to learn a bunch of new tactics -- the
ones we just learnt (`exact`, `intro`, `have`, `apply`) will be perfect.

The levels in proposition world are "back to normal", we're proving
theorems, not constructing elements of sets. Or are we?

If you delete the sorry below then your local context will look like this:

```
P Q : Prop,
p : P,
h : P → Q
⊢ Q
```

In this situation, we have true/false statements $P$ and $Q$,
a proof $p$ of $P$, and $h$ is the hypothesis that $P\implies Q$.
Our goal is to construct a proof of $Q$. It's clear what to do
*mathematically* to solve this goal, $P$ is true and $P$ implies $Q$
so $Q$ is true. But how to do it in Lean?

Adopting a point of view wholly unfamiliar to many mathematicians,
Lean interprets the hypothesis $h$ as a function from proofs
of $P$ to proofs of $Q$, so the rather surprising approach

`exact h(p),`

works to close the goal.

Note that `exact h(P),` (with a capital P) won't work;
this is a common error I see from beginners. "We're trying to solve `P`
so it's exactly `P`". The goal states the *theorem*, your job is to
construct the *proof*. $P$ is not a proof of $P$, it's $p$ that is a proof of $P$.

In Lean, Propositions, like sets, are types, and proofs, like elements of sets, are terms.

## Level 1: the `exact` tactic.
-/

/- Lemma : no-side-bar
If $P$ is true, and $P\implies Q$ is also true, then $Q$ is true.
-/
example (P Q : Prop) (p : P) (h : P → Q) : Q :=
begin
exact h(p),

end


-- Monotonia_de_la_suma_por_la_izquierda.lean
-- Monotonía de la suma por la izquierda
-- José A. Alonso Jiménez
-- Sevilla, 14 de agosto de 2020
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si a, b y c son números reles tales que
-- a ≤ b, entonces c + a ≤ c + b.
--
-- Indicación: Se puede usar el lema
--    sub_nonneg : 0 ≤ a - b ↔ b ≤ a
-- ----------------------------------------------------------------------

import data.real.basic
variables {a b c : ℝ}

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

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  have h : (c + b) - (c + a) = b - a,
  { ring, },
  { rw h,
    rw sub_nonneg,
    exact hab, },
end

-- Comentario: Se ha usado el lema
-- + sub_nonneg : 0 ≤ a - b ↔ b ≤ a

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

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  calc 0   ≤ b - a           : by exact sub_nonneg.mpr hab
       ... = c + b - (c + a) : by exact (add_sub_add_left_eq_sub b a c).symm,
end

-- Comentario: Se usa el lema
-- + add_sub_add_left_eq_sub : c + a - (c + b) = a - b

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

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  calc 0   ≤ b - a           : sub_nonneg.mpr hab
       ... = c + b - (c + a) : (add_sub_add_left_eq_sub b a c).symm,
end

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

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  calc 0   ≤ b - a           : sub_nonneg.mpr hab
       ... = c + b - (c + a) : by ring
end

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

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  simp,
  exact hab,
end

-- 6ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  simp [hab],
end

-- Comentario:
-- + La táctica (simp [h]) aplica reglas de simplificación, ampliadas con
--   h, a la conclusión.

-- 7ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
begin
  simp [hab],
end

-- 8ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
by simp [hab]

-- 9ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
add_le_add_left hab c

-- Comentario: Se ha usado el lema
-- + add_le_add_left : a ≤ b → ∀ (c : ℝ), c + a ≤ c + b

-- 10ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
by linarith

-- 11ª demostración
-- ===============

example
  (hab : a ≤ b)
  : c + a ≤ c + b :=
by finish


-- Pruebas por inducción 1: 0 + n = n
-- ==================================

-- ----------------------------------------------------
-- Ej. 1. Sean m y n números naturales. Demostrar que
--    m + 0 = m
-- ----------------------------------------------------

import tactic

open nat

variables (m n : ℕ)

-- 1ª demostración
example : m + 0 = m :=
nat.add_zero m

-- 2ª demostración
example : m + 0 = m :=
rfl

-- ----------------------------------------------------
-- Ej. 2. Sean m y n números naturales. Demostrar que
--    m + (n + 1) = (m + n) + 1
-- ----------------------------------------------------

-- 1ª demostración
example : m + (n + 1) = (m + n) + 1 :=
add_succ m n

-- 2ª demostración
example : m + (n + 1) = (m + n) + 1 :=
rfl

-- ----------------------------------------------------
-- Ej. 3. Sean n un número natural. Demostrar que
--    0 + n = n
-- ----------------------------------------------------

-- 1ª demostración
example : 0 + n = n :=
begin
  induction n with n HI,
  { rw nat.add_zero, },
  { rw add_succ,
    rw HI, },
end

-- 2ª demostración
example : 0 + n = n :=
begin
  induction n with n HI,
  { rw nat.add_zero, },
  { rw [add_succ, HI], },
end

-- 3ª demostración
example : 0 + n = n :=
begin
  induction n with n HI,
  { simp, },
  { simp [add_succ, HI], },
end

-- 4ª demostración
example : 0 + n = n :=
begin
  induction n with n HI,
  { simp only [nat.add_zero], },
  { simp only [add_succ, HI], },
end

-- 5ª demostración
example : 0 + n = n :=
by induction n;
   simp only [*,
              nat.add_zero,
              add_succ]

-- 6ª demostración
example : 0 + n = n :=
by induction n;
   simp

-- 7ª demostración
example : 0 + n = n :=
nat.rec_on n
  ( show 0 + 0 = 0, from nat.add_zero 0)
  ( assume n,
    assume HI : 0 + n = n,
    show 0 + succ n = succ n, from
      calc
        0 + succ n = succ (0 + n) : by rw add_succ
               ... = succ n       : by rw HI)

-- 8ª demostración
example : 0 + n = n :=
nat.rec_on n
  ( show 0 + 0 = 0, from rfl)
  ( assume n,
    assume HI : 0 + n = n,
    show 0 + succ n = succ n, from
      calc
        0 + succ n = succ (0 + n) : rfl
               ... = succ n       : by rw HI)

-- 9ª demostración
example : 0 + n = n :=
nat.rec_on n rfl (λ n HI, by rw [add_succ, HI])

-- 10ª demostración
example : 0 + n = n :=
nat.rec_on n rfl (λ n HI, by simp only [add_succ, HI])

-- 11ª demostración
example : 0 + n = n :=
-- by library_search
zero_add n

-- 12ª demostración
example : 0 + n = n :=
-- by hint
by simp

-- 13ª demostración
example : 0 + n = n :=
by finish

-- 14ª demostración
example : 0 + n = n :=
by linarith

-- 15ª demostración
example : 0 + n = n :=
by nlinarith

-- 16ª demostración
example : 0 + n = n :=
by norm_num

-- 17ª demostración
example : 0 + n = n :=
by ring

-- 18ª demostración
example : 0 + n = n :=
by omega

-- 19ª demostración
example : 0 + n = n :=
by tidy

-- 20ª demostración
example : 0 + n = n :=
-- by tidy ?
by simp at *

-- 21ª demostración
lemma cero_mas : ∀ n : ℕ, 0 + n = n
| 0     := rfl
| (n+1) := congr_arg succ (cero_mas n)

-- 22ª demostración
lemma cero_mas2 : ∀ n : ℕ, 0 + n = n
| 0     := by simp
| (n+1) := by simp

-- 23ª demostración
lemma cero_mas3 : ∀ n : ℕ, 0 + n = n
| 0     := by simp only [add_zero]
| (n+1) := by simp only [add_zero, add_succ, cero_mas3 n]

-- 24ª demostración
lemma cero_mas4 : ∀ n : ℕ, 0 + n = n
| 0     := by rw [add_zero]
| (n+1) := by rw [add_succ, cero_mas4 n]


import tactic.ring
import data.real.basic
import algebra.parity

example (x y : ℕ) : x + y = y + x := by ring
example (x y : ℕ) : x + y + y = 2 * y + x := by ring
example (x y : ℕ) : x + id y = y + id x := by ring!
example {α} [comm_ring α] (x y : α) : x + y + y - x = 2 * y := by ring
example (x y : ℚ) : x / 2 + x / 2 = x := by ring
example (x y : ℚ) : (x + y) ^ 3 = x ^ 3 + y ^ 3 + 3 * (x * y ^ 2 + x ^ 2 * y) := by ring
example (x y : ℝ) : (x + y) ^ 3 = x ^ 3 + y ^ 3 + 3 * (x * y ^ 2 + x ^ 2 * y) := by ring
example {α} [comm_semiring α] (x : α) : (x + 1) ^ 6 = (1 + x) ^ 6 := by try_for 15000 {ring}
example (n : ℕ) : (n / 2) + (n / 2) = 2 * (n / 2) := by ring
example {α} [field α] [char_zero α] (a : α) : a / 2 = a / 2 := by ring
example {α} [linear_ordered_field α] (a b c : α) :
  a * (-c / b) * (-c / b) + -c + c = a * (c / b * (c / b)) := by ring
example {α} [linear_ordered_field α] (a b c : α) :
  b ^ 2 - 4 * c * a = -(4 * c * a) + b ^ 2 := by ring
example (x : ℚ) : x ^ (2 + 2) = x^4 := by ring_nf -- TODO: ring should work?
example {α} [comm_ring α] (x : α) : x ^ 2 = x * x := by ring
example {α} [linear_ordered_field α] (a b c : α) :
  b ^ 2 - 4 * c * a = -(4 * c * a) + b ^ 2 := by ring
example {α} [linear_ordered_field α] (a b c : α) :
  b ^ 2 - 4 * a * c = 4 * a * 0 + b * b - 4 * a * c := by ring
example {α} [comm_semiring α] (x y z : α) (n : ℕ) :
  (x + y) * (z * (y * y) + (x * x ^ n + (1 + ↑n) * x ^ n * y)) =
    x * (x * x ^ n) + ((2 + ↑n) * (x * x ^ n) * y + (x * z + (z * y + (1 + ↑n) * x ^ n)) * (y * y)) := by ring
example {α} [comm_ring α] (a b c d e : α) :
  (-(a * b) + c + d) * e = (c + (d + -a * b)) * e := by ring
example (a n s: ℕ) : a * (n - s) = (n - s) * a := by ring

example (x y z : ℚ) (hx : x ≠ 0) (hy : y ≠ 0) (hz : z ≠ 0) :
  x / (y / z) + y ⁻¹ + 1 / (y * -x) = -1/ (x * y) + (x * z + 1) / y :=
begin
  field_simp,
  ring
end

example (a b c d x y : ℚ) (hx : x ≠ 0) (hy : y ≠ 0) :
  a + b / x - c / x^2 + d / x^3 = a + x⁻¹ * (y * b / y + (d / x - c) / x) :=
begin
  field_simp,
  ring
end

example : (876544 : ℤ) * -1 + (1000000 - 123456) = 0 := by ring

example (x y : ℝ) (hx : x ≠ 0) (hy : y ≠ 0) :
  2 * x ^ 3 * 2 / (24 * x) = x ^ 2 / 6 :=
begin
  field_simp,
  ring
end

-- this proof style is not recommended practice
example (A B : ℕ) (H : B * A = 2) : A * B = 2 := by {ring_nf, exact H}

example (a : ℤ) : odd ((2 * a + 1) ^ 2) :=
begin
  use 2 * a ^ 2 + 2 * a,
  ring_nf,
end

example {x y : ℝ}
  (hxy : -y ^ 2 + x ^ 2 = -(5 * y) + 5 * x) :
  x ^ 2 - y ^ 2 = 5 * x - 5 * y :=
begin
  ring_nf at hxy ⊢,
  exact hxy
end

example {α} [field α] {x y : α}
  (h : 0 = (1 - x) ^ 2 * (x * (2 ^ 2 * y ^ 2 + 4 * (1 - x) ^ 2))) :
  0 = x * ((2 ^ 2 * y ^ 2 + 4 * (1 - x) ^ 2) * (1 - x) ^ 2) :=
by transitivity; [exact h, ring]

-- `ring_nf` should descend into the subexpressions `x * -a` and `-a * x`:
example {a x : ℚ} : x * -a = - a * x := by ring_nf


import algebra.field
import tactic

/--
Linear change of variable defined in GTM106,
also viewed as a matrix
```
u^2   0   r
u^2*s u^3 t
0     0   1
```
-/
@[ext]
structure linear_change_of_variable (K : Type*) [field K] :=
mk :: (u r s t : K) (hu : u ≠ 0)

namespace linear_change_of_variable

@[simp]
lemma u_non_zero {K : Type*} [field K]
(C : linear_change_of_variable K) : C.u ≠ 0 := C.hu

-- ================
-- Linear change of variables form a group
-- ================

def identity (K : Type*) [field K] : linear_change_of_variable K :=
⟨ 1, 0, 0, 0, by simp ⟩

def composite {K : Type*} [field K]
(C C' : linear_change_of_variable K) : linear_change_of_variable K :=
⟨ C.u*C'.u, C.r*C'.u^2 + C'.r, C'.u*C.s + C'.s,
  C.t*C'.u^3 + C.r*C'.s*C'.u^2 + C'.t, by simp ⟩

lemma comp_assoc {K : Type*} [field K]
(C C' C'' : linear_change_of_variable K) : ((C.composite C').composite C'') = (C.composite (C'.composite C'')) :=
begin
  simp [composite, ext_iff],
  split, { ring, }, split, { ring, }, split, { ring, }, ring,
end

@[simp]
lemma id_comp {K : Type*} [field K]
(C : linear_change_of_variable K) : (identity K).composite C = C :=
begin
  simp [composite, identity, ext_iff],
end

@[simp]
lemma comp_id {K : Type*} [field K]
(C : linear_change_of_variable K) : C.composite (identity K) = C :=
begin
  simp [composite, identity, ext_iff],
end

def inverse {K : Type*} [field K]
(C : linear_change_of_variable K) : linear_change_of_variable K :=
⟨ 1/C.u, -C.r/C.u^2, -C.s/C.u, (C.r*C.s-C.t)/C.u^3, by simp ⟩

@[simp]
lemma inv_comp {K : Type*} [field K]
(C : linear_change_of_variable K) : C.inverse.composite C = identity K :=
begin
  simp [composite, inverse, identity, ext_iff],
  split, { field_simp, ring, },
  field_simp [pow_succ], ring,
end

@[simp]
lemma comp_inv {K : Type*} [field K]
(C : linear_change_of_variable K) : C.composite C.inverse = identity K :=
begin
  simp [composite, inverse, identity, ext_iff],
  split, { field_simp [pow_succ], }, split, { field_simp [mul_comm], },
  field_simp [pow_succ], ring,
end

instance to_group (K : Type*) [field K] : group (linear_change_of_variable K)
:= ⟨ composite, comp_assoc, identity K, id_comp, comp_id, inverse,
  (λ C C', C.composite (C'.inverse)), by {
    intros _ _, simp only [has_mul.mul, mul_one_class.mul],
  }, inv_comp ⟩

end linear_change_of_variable


-- Pruebas de la conmutatividad de la intersección
-- ===============================================

-- ----------------------------------------------------
-- Ej. 1. Demostrar
--    A ∩ B ⊆ B ∩ A
-- ----------------------------------------------------

import data.set

variable  {U : Type}
variables A B : set U
variable  x : U

open set

-- 1ª demostración
example : A ∩ B ⊆ B ∩ A :=
begin
  intros x h,
  simp at *,
  split,
  { exact h.right, },
  { exact h.left,  },
end

-- 2ª demostración
example : A ∩ B ⊆ B ∩ A :=
begin
  intros x h,
  split,
  { exact h.right, },
  { exact h.left,  },
end

-- 3ª demostración
example : A ∩ B ⊆ B ∩ A :=
begin
  rintros x ⟨h1, h2⟩,
  split,
  { exact h2, },
  { exact h1, },
end

-- 4ª demostración
example : A ∩ B ⊆ B ∩ A :=
begin
  rintros x ⟨h1, h2⟩,
  exact ⟨h2, h1⟩,
end

-- 5ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
assume h : x ∈ A ∩ B,
have h1 : x ∈ A, from and.left h,
have h2 : x ∈ B, from and.right h,
show x ∈ B ∩ A,  from and.intro h2 h1

-- 6ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
assume h : x ∈ A ∩ B,
have h1 : x ∈ A ∧ x ∈ B, from h,
have h2 : x ∈ B ∧ x ∈ A, from and.comm.mp h1,
show x ∈ B ∩ A,          from h2

-- 7ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
assume h : x ∈ A ∩ B,
show x ∈ B ∩ A, from and.comm.mp h

-- 8ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
assume h : x ∈ A ∩ B,
and.comm.mp h

-- 9ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
λ h, and.comm.mp h

-- 10ª demostración
example : A ∩ B ⊆ B ∩ A :=
assume x,
and.comm.mp

-- 10ª demostración
example : A ∩ B ⊆ B ∩ A :=
λ _, and.comm.mp

-- 11ª demostración
example : A ∩ B ⊆ B ∩ A :=
-- by hint
by finish

-- 12ª demostración
lemma aux : A ∩ B ⊆ B ∩ A :=
by simp

-- ----------------------------------------------------
-- Ej. 2. Demostrar
--    A ∩ B = B ∩ A
-- ----------------------------------------------------

-- 1ª demostración
example : A ∩ B = B ∩ A :=
begin
  apply eq_of_subset_of_subset,
  { exact aux A B, },
  { exact aux B A, },
end

-- 2ª demostración
example : A ∩ B = B ∩ A :=
eq_of_subset_of_subset (aux A B) (aux B A)

-- 3ª demostración
example : A ∩ B = B ∩ A :=
-- by library_search
inter_comm A B

-- 4ª demostración
example : A ∩ B = B ∩ A :=
-- by hint
by finish


/-
Copyright (c) 2022 Jon Eugster. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jon Eugster
-/
import algebra.char_p.basic
import ring_theory.ideal.local_ring
import algebra.is_prime_pow
import data.nat.factorization.basic

/-!
# Characteristics of local rings

## Main result

- `char_p_zero_or_prime_power`: In a commutative local ring the characteristics is either
  zero or a prime power.

-/

/-- In a local ring the characteristics is either zero or a prime power. -/
theorem char_p_zero_or_prime_power (R : Type*) [comm_ring R] [local_ring R] (q : ℕ)
  [char_R_q : char_p R q] : q = 0 ∨ is_prime_pow q :=
begin
  /- Assume `q := char(R)` is not zero. -/
  apply or_iff_not_imp_left.2,
  intro q_pos,
  let K := local_ring.residue_field R,
  haveI RM_char := ring_char.char_p K,

  let r := ring_char K,
  let n := (q.factorization) r,
  /- `r := char(R/m)` is either prime or zero: -/
  cases char_p.char_is_prime_or_zero K r with r_prime r_zero,
  { let a := q / (r ^ n),
    /- If `r` is prime, we can write it as `r = a * q^n` ... -/
    have q_eq_a_mul_rn : q = r ^ n * a := by rw nat.mul_div_cancel' (nat.ord_proj_dvd q r),
    have r_ne_dvd_a := nat.not_dvd_ord_compl r_prime q_pos,

    have rn_dvd_q: r ^ n ∣ q := ⟨a, q_eq_a_mul_rn⟩,
    rw mul_comm at q_eq_a_mul_rn,
    have a_dvd_q : a ∣ q := ⟨r ^ n, q_eq_a_mul_rn⟩,
    /- ... where `a` is a unit. -/
    have a_unit : is_unit (a : R) :=
    begin
      by_contradiction g,
      rw ←mem_nonunits_iff at g,
      rw ←local_ring.mem_maximal_ideal at g,
      have a_cast_zero := (ideal.quotient.eq_zero_iff_mem).2 g,
      rw map_nat_cast at a_cast_zero,
      have r_dvd_a := (ring_char.spec K a).1 a_cast_zero,
      exact absurd r_dvd_a r_ne_dvd_a,
    end,
    /- Let `b` be the inverse of `a`. -/
    cases a_unit.exists_left_inv with a_inv h_inv_mul_a,
    have rn_cast_zero : ↑(r ^ n) = (0 : R) :=
    begin
      rw [nat.cast_pow, ←@mul_one R _ (r ^ n), mul_comm,
        ←(classical.some_spec a_unit.exists_left_inv), mul_assoc, ←nat.cast_pow, ←nat.cast_mul,
        ←q_eq_a_mul_rn, char_p.cast_eq_zero R q],
      simp,
    end,
    have q_eq_rn := nat.dvd_antisymm ((char_p.cast_eq_zero_iff R q (r ^ n)).mp rn_cast_zero)
      rn_dvd_q,
    have n_pos : n ≠ 0,
      from λ n_zero, absurd (by simpa [n_zero] using q_eq_rn) (char_p.char_ne_one R q),

    /- Definition of prime power: `∃ r n, prime r ∧ 0 < n ∧ r ^ n = q`. -/
    exact ⟨r, ⟨n, ⟨r_prime.prime, ⟨pos_iff_ne_zero.mpr n_pos, q_eq_rn.symm⟩⟩⟩⟩},
  { haveI K_char_p_0 := ring_char.of_eq r_zero,
    haveI K_char_zero: char_zero K := char_p.char_p_to_char_zero K,
    haveI R_char_zero := ring_hom.char_zero (local_ring.residue R),
    /- Finally, `r = 0` would lead to a contradiction: -/
    have q_zero := char_p.eq R char_R_q (char_p.of_char_zero R),
    exact absurd q_zero q_pos}
end


/-
Copyright (c) 2023 Mantas Bakšys, Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mantas Bakšys, Yaël Dillies
-/
import algebra.big_operators.order
import algebra.order.rearrangement
import group_theory.perm.cycle.basic

/-!
# Chebyshev's sum inequality

This file proves the Chebyshev sum inequality.

Chebyshev's inequality states `(∑ i in s, f i) * (∑ i in s, g i) ≤ s.card * ∑ i in s, f i * g i`
when `f g : ι → α` monovary, and the reverse inequality when `f` and `g` antivary.

## Main declarations

* `monovary_on.sum_mul_sum_le_card_mul_sum`: Chebyshev's inequality.
* `antivary_on.card_mul_sum_le_sum_mul_sum`: Chebyshev's inequality, dual version.
* `sq_sum_le_card_mul_sum_sq`: Special case of Chebyshev's inequality when `f = g`.

## Implementation notes

In fact, we don't need much compatibility between the addition and multiplication of `α`, so we can
actually decouple them by replacing multiplication with scalar multiplication and making `f` and `g`
land in different types.
As a bonus, this makes the dual statement trivial. The multiplication versions are provided for
convenience.

The case for `monotone`/`antitone` pairs of functions over a `linear_order` is not deduced in this
file because it is easily deducible from the `monovary` API.
-/

open equiv equiv.perm finset function order_dual
open_locale big_operators

variables {ι α β : Type*}

/-! ### Scalar multiplication versions -/

section smul
variables [linear_ordered_ring α] [linear_ordered_add_comm_group β] [module α β]
  [ordered_smul α β] {s : finset ι} {σ : perm ι} {f : ι → α} {g : ι → β}

/-- **Chebyshev's Sum Inequality**: When `f` and `g` monovary together (eg they are both
monotone/antitone), the scalar product of their sum is less than the size of the set times their
scalar product. -/
lemma monovary_on.sum_smul_sum_le_card_smul_sum (hfg : monovary_on f g s) :
  (∑ i in s, f i) • ∑ i in s, g i ≤ s.card • ∑ i in s, f i • g i :=
begin
  classical,
  obtain ⟨σ, hσ, hs⟩ := s.countable_to_set.exists_cycle_on,
  rw [←card_range s.card, sum_smul_sum_eq_sum_perm hσ],
  exact sum_le_card_nsmul _ _ _ (λ n _, hfg.sum_smul_comp_perm_le_sum_smul $ λ x hx, hs $ λ h, hx $
    is_fixed_pt.perm_pow h _),
end

/-- **Chebyshev's Sum Inequality**: When `f` and `g` antivary together (eg one is monotone, the
other is antitone), the scalar product of their sum is less than the size of the set times their
scalar product. -/
lemma antivary_on.card_smul_sum_le_sum_smul_sum (hfg : antivary_on f g s) :
  s.card • ∑ i in s, f i • g i ≤ (∑ i in s, f i) • ∑ i in s, g i :=
by convert hfg.dual_right.sum_smul_sum_le_card_smul_sum

variables [fintype ι]

/-- **Chebyshev's Sum Inequality**: When `f` and `g` monovary together (eg they are both
monotone/antitone), the scalar product of their sum is less than the size of the set times their
scalar product. -/
lemma monovary.sum_smul_sum_le_card_smul_sum (hfg : monovary f g) :
  (∑ i, f i) • ∑ i, g i ≤ fintype.card ι • ∑ i, f i • g i :=
(hfg.monovary_on _).sum_smul_sum_le_card_smul_sum

/-- **Chebyshev's Sum Inequality**: When `f` and `g` antivary together (eg one is monotone, the
other is antitone), the scalar product of their sum is less than the size of the set times their
scalar product. -/
lemma antivary.card_smul_sum_le_sum_smul_sum (hfg : antivary f g) :
  fintype.card ι • ∑ i, f i • g i ≤ (∑ i, f i) • ∑ i, g i :=
by convert (hfg.dual_right.monovary_on _).sum_smul_sum_le_card_smul_sum

end smul

/-!
### Multiplication versions

Special cases of the above when scalar multiplication is actually multiplication.
-/

section mul
variables [linear_ordered_ring α] {s : finset ι} {σ : perm ι} {f g : ι → α}

/-- **Chebyshev's Sum Inequality**: When `f` and `g` monovary together (eg they are both
monotone/antitone), the product of their sum is less than the size of the set times their scalar
product. -/
lemma monovary_on.sum_mul_sum_le_card_mul_sum (hfg : monovary_on f g s) :
  (∑ i in s, f i) * (∑ i in s, g i) ≤ s.card * ∑ i in s, f i * g i :=
by { rw ←nsmul_eq_mul, exact hfg.sum_smul_sum_le_card_smul_sum }

/-- **Chebyshev's Sum Inequality**: When `f` and `g` antivary together (eg one is monotone, the
other is antitone), the product of their sum is greater than the size of the set times their scalar
product. -/
lemma antivary_on.card_mul_sum_le_sum_mul_sum (hfg : antivary_on f g s) :
  (s.card : α) * ∑ i in s, f i * g i ≤ (∑ i in s, f i) * (∑ i in s, g i) :=
by { rw ←nsmul_eq_mul, exact hfg.card_smul_sum_le_sum_smul_sum }

/-- Special case of **Chebyshev's Sum Inequality** or the **Cauchy-Schwarz Inequality**: The square
of the sum is less than the size of the set times the sum of the squares. -/
lemma sq_sum_le_card_mul_sum_sq : (∑ i in s, f i)^2 ≤ s.card * ∑ i in s, f i ^ 2 :=
by { simp_rw sq, exact (monovary_on_self _ _).sum_mul_sum_le_card_mul_sum }

variables [fintype ι]

/-- **Chebyshev's Sum Inequality**: When `f` and `g` monovary together (eg they are both
monotone/antitone), the product of their sum is less than the size of the set times their scalar
product. -/
lemma monovary.sum_mul_sum_le_card_mul_sum (hfg : monovary f g) :
  (∑ i, f i) * (∑ i, g i) ≤ fintype.card ι * ∑ i, f i * g i :=
(hfg.monovary_on _).sum_mul_sum_le_card_mul_sum

/-- **Chebyshev's Sum Inequality**: When `f` and `g` antivary together (eg one is monotone, the
other is antitone), the product of their sum is less than the size of the set times their scalar
product. -/
lemma antivary.card_mul_sum_le_sum_mul_sum (hfg : antivary f g) :
  (fintype.card ι : α) * ∑ i, f i * g i ≤ (∑ i, f i) * (∑ i, g i) :=
(hfg.antivary_on _).card_mul_sum_le_sum_mul_sum

end mul

variables [linear_ordered_field α] {s : finset ι} {f : ι → α}

lemma sum_div_card_sq_le_sum_sq_div_card :
  ((∑ i in s, f i) / s.card) ^ 2 ≤ (∑ i in s, f i ^ 2) / s.card :=
begin
  obtain rfl | hs := s.eq_empty_or_nonempty,
  { simp },
  rw [←card_pos, ←@nat.cast_pos α] at hs,
  rw [div_pow, div_le_div_iff (sq_pos_of_ne_zero _ hs.ne') hs, sq (s.card : α), mul_left_comm,
    ←mul_assoc],
  exact mul_le_mul_of_nonneg_right (sq_sum_le_card_mul_sum_sq) hs.le,
end


/- LoVe Exercise 3: Structured Proofs and Proof Terms -/

import .lovelib

namespace LoVe

/- Question 1: Chain of Equalities -/

/- 1.1. Write the following proof using `calc`.

    (a + b) * (a + b)
    = a * (a + b) + b * (a + b)
    = a * a + a * b + b * a + b * b
    = a * a + a * b + a * b + b * b
    = a * a + 2 * a * b + b * b

Hint: You might need `rw`, `simp`, `ac_refl`, and the lemmas `mul_add`,
`add_mul`, and `two_mul`. -/

lemma binomial_square (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
calc (a + b) * (a + b) = a * (a + b) + b * (a + b) :
  by simp [add_mul]
... = a * a + a * b + b * a + b * b :
  by simp [mul_add]
... = a * a + a * b + a * b + b * b :
  by ac_refl
... = a * a + 2 * a * b + b * b :
  by simp [two_mul, add_mul]; ac_refl

/- 1.2. Prove the same argument again, this time as a structured proof. Try to
reuse as much of the above proof idea as possible. -/

lemma binomial_square₂ (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
have h1 : (a + b) * (a + b) = a * (a + b) + b * (a + b) :=
  by simp [add_mul],
have h2 : a * (a + b) + b * (a + b) = a * a + a * b + b * a + b * b :=
  by simp [mul_add],
have h3 : a * a + a * b + b * a + b * b = a * a + a * b + a * b + b * b :=
  by ac_refl,
have h4 : a * a + a * b + a * b + b * b = a * a + 2 * a * b + b * b :=
  by simp [two_mul, add_mul]; ac_refl,
show _,
  by rw [h1, h2, h3, h4]

/- 1.3 (**optional**). Prove the same lemma again, this time using tactics. -/

lemma binomial_square₃ (a b : ℕ) :
  (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
begin
  simp [add_mul, mul_add, two_mul],
  ac_refl
end

/- Question 2: Connectives and Quantifiers -/

/- 2.1. Supply structured proofs of the following lemmas. -/

lemma I (a : Prop) :
  a → a :=
assume ha,
show a, from ha

lemma K (a b : Prop) :
  a → b → b :=
assume ha hb,
show b, from hb

lemma C (a b c : Prop) :
  (a → b → c) → b → a → c :=
assume hg hb ha,
show c, from hg ha hb

lemma proj_1st (a : Prop) :
  a → a → a :=
assume ha ha',
show a, from ha

-- please give a different answer than for `proj_1st`
lemma proj_2nd (a : Prop) :
  a → a → a :=
assume ha ha',
show a, from ha'

lemma some_nonsense (a b c : Prop) :
  (a → b → c) → a → (a → c) → b → c :=
assume hg ha hf hb,
have hc : c := hf ha,
show c, from hc

/- 2.2. Supply a structured proof of the contraposition rule. -/

lemma contrapositive (a b : Prop) :
  (a → b) → ¬ b → ¬ a :=
assume hab hnb ha,
have hb : b := hab ha,
show false, from hnb hb

end LoVe


/-
The at-least-k boolean function.

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

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

open assignment clause amk distinct
open list nat

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

def alk (k : nat) : constraint := λ l, l.count tt ≥ k

namespace alk

@[simp] theorem eval_zero : ∀ (τ : assignment V) (l : list (literal V)),
  (alk 0).eval τ l = tt :=
assume τ l, by simp only [constraint.eval, alk, ge_iff_le, zero_le, to_bool_true_eq_tt]

@[simp] theorem eval_nil_ff_iff_pos {k : nat} :
  ∀ τ, ((alk k).eval τ ([] : list (literal V)) = ff ↔ k > 0) :=
assume τ, by simp [constraint.eval, alk, pos_iff_ne_zero]

@[simp] theorem eval_nil_tt_iff_zero {k : nat} :
  ∀ τ, ((alk k).eval τ ([] : list (literal V)) = tt ↔ k = 0) :=
assume τ, by simp [constraint.eval, alk]

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

theorem eval_cons_pos : lit.eval τ = tt →
  ∀ l, (alk (k + 1)).eval τ (lit :: l) = (alk k).eval τ l :=
assume hlit l, by simp [constraint.eval, alk, hlit, succ_le_succ_iff]

theorem eval_cons_neg : lit.eval τ = ff →
  ∀ l, (alk k).eval τ (lit :: l) = (alk k).eval τ l :=
assume hlit l,
by simp only [constraint.eval, alk, hlit, map, count_cons_of_ne, ne.def, not_false_iff]

/-
theorem eval_tt_of_gt :
  (alk k).eval τ l = tt → ∀ ⦃j : nat⦄, j < k → (alk j).eval τ l = tt :=
begin
  intros halk j hj,
  sorry
end
-/

-- TODO: supply k as an explicit arg, or ∀ in conclusion
theorem eval_take_tail_pos {i : nat} {Hi : i < length l} :
  (l.nth_le i Hi).eval τ = tt →
  (alk (k + 1)).eval τ (l.take (i + 1)) = (alk k).eval τ (l.take i) :=
begin
  intro hl,
  induction i with i ih generalizing l k,
  { rw nth_le_zero at hl,
    simp [take_one_of_ne_nil (ne_nil_of_length_pos Hi), hl, constraint.eval, alk] },
  { rcases exists_cons_of_ne_nil (ne_nil_of_length_pos (pos_of_gt Hi)) with ⟨l₁, ls, rfl⟩,
    rw nth_le at hl,
    cases h₁ : l₁.eval τ,
    { simp only [take, eval_cons_neg h₁, ih hl] },
    { cases k,
      { simp [constraint.eval, alk, h₁, succ_le_succ_iff] },
      { simp [take, eval_cons_pos h₁],
        exact ih hl } } }
end

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

theorem alo_eval_tt_iff_exists_eval_tt :
  (alk 1).eval τ l = tt ↔ ∃ {lit : literal V}, lit ∈ l ∧ lit.eval τ = tt :=
begin
  induction l with lit₁ ls ih,
  { simp only [eval_nil_tt_iff_zero, nat.one_ne_zero, not_mem_nil, false_and, exists_false] },
  { cases hlit₁ : lit₁.eval τ,
    { rw eval_cons_neg hlit₁,
      split,
      { intro h,
        rcases ih.mp h with ⟨lit, hmem, heval⟩,
        exact ⟨lit, mem_cons_of_mem _ hmem, heval⟩ },
      { rintro ⟨lit, hmem, heval⟩,
        rcases eq_or_mem_of_mem_cons hmem with (rfl | hmem),
        { rw hlit₁ at heval, contradiction },
        { exact ih.mpr ⟨lit, hmem, heval⟩ } } },
    { split,
      { intro _, use [lit₁, mem_cons_self _ _, hlit₁] },
      { intro _, rw eval_cons_pos hlit₁, exact eval_zero τ ls } } }
end

theorem amo_take_of_tt {i : nat} (Hi : i < length l) :
  (l.nth_le i Hi).eval τ = tt → (alk 1).eval τ (l.take (i + 1)) = tt :=
assume ht, eval_zero τ l ▸ eval_take_tail_pos ht

theorem alo_take_tail {i : nat} (Hi : i < length l) :
  (alk 1).eval τ (l.take (i + 1)) = tt →
  ((l.nth_le i Hi).eval τ = tt ∨ (alk 1).eval τ (l.take i) = tt) :=
begin
  intro hlk,
  cases hl : (l.nth_le i Hi).eval τ,
  { rw eval_take_tail_neg hl at hlk,
    exact or.inr hlk },
  { exact or.inl rfl }
end

theorem eval_tt_of_ge_of_eval_tt {k₁ k₂ : nat} : k₁ ≥ k₂ →
  (alk k₁).eval τ l = tt → (alk k₂).eval τ l = tt :=
begin
  simp only [constraint.eval, alk, ge_iff_le, to_bool_iff],
  intros hk h₁,
  exact le_trans hk h₁
end

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

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

theorem amk_alk_take {k : nat} {l : list (literal V)} {i : nat} :
  i ≤ length l → ∀ {τ : assignment V}, (alk k).eval τ (l.take i) = tt →
  (amk k).eval τ l = tt → (amk 0).eval τ (l.drop i) = tt :=
begin
  intros hi τ htake hamk,
  induction l with l₁ ls ih generalizing k i,
  { simp only [drop_eq_nil_of_le, length, zero_le, amk.eval_nil] },
  { cases i,
    { simp at htake, subst htake,
      rw drop,
      exact hamk },
    { cases k,
      { exact eval_drop hamk _ },
      { rw [length, succ_le_succ_iff] at hi,
        cases hl₁ : (l₁.eval τ),
        { rw [take, eval_cons_neg hl₁] at htake,
          rw amk.eval_cons_neg hl₁ at hamk,
          exact ih hi htake hamk },
        { rw [take, eval_cons_pos hl₁] at htake,
          rw amk.eval_cons_pos hl₁ at hamk,
          exact ih hi htake hamk } } } }
end

theorem amk_eval_eq_alk_succ_eval (k : nat) (τ : assignment V) (l : list (literal V)) :
  (amk k).eval τ l = !((alk (k + 1)).eval τ l) :=
begin
  induction l with l₁ ls ih generalizing k,
  { apply symm, simp },
  { cases h₁ : l₁.eval τ,
    { rw [amk.eval_cons_neg h₁, alk.eval_cons_neg h₁],
      exact ih k },
    { cases k,
      { rw [amk.eval_cons_pos_zero h₁, alk.eval_cons_pos h₁, eval_zero],
        exact bool.bnot_tt.symm },
      { rw [amk.eval_cons_pos h₁, alk.eval_cons_pos h₁],
        exact ih k } } }
end

theorem alk_split {k : nat} {l : list (literal V)} {τ : assignment V} :
  (alk (k + 1)).eval τ l = tt → ∃ {i : nat} (Hi : i < length l),
  (amk 0).eval τ (l.take (i - 1)) = tt ∧ (l.nth_le i Hi).eval τ = tt ∧
  (alk k).eval τ (l.drop (i + 1)) = tt :=
begin
  intro halk,
  induction l with l₁ ls ih generalizing k,
  { simp at halk |-, assumption },
  { cases h₁ : l₁.eval τ,
    { rw eval_cons_neg h₁ at halk,
      rcases ih halk with ⟨i, Hi, htake, he, hdrop⟩,
      use i + 1,
      simp [Hi, hdrop],
      cases i,
      { simpa },
      { simp at htake, simpa [amk.eval_cons_neg h₁, htake] } },
    { rw eval_cons_pos h₁ at halk, use 0, simpa [h₁] } }
end

theorem alk_of_amk_of_gt {j : nat} {l : list (literal V)} {τ : assignment V} :
  (amk j).eval τ l = tt → ∀ {k}, j < k → (alk k).eval τ l = ff :=
begin
  induction l with l₁ ls ih generalizing j,
  { simp, intros k hk, exact pos_of_gt hk },
  { intros hmk k hk,
    cases j,
    { have ihred := ih (amz_of_amz_cons hmk) hk,
      rw amz_eval_tt_iff_forall_eval_ff at hmk,
      rw eval_cons_neg (hmk (mem_cons_self _ _)),
      exact ihred },
    { cases hl₁ : l₁.eval τ,
      { rw amk.eval_cons_neg hl₁ at hmk,
        rw eval_cons_neg hl₁,
        exact ih hmk hk },
      { cases k with k,
        { linarith },
        { rw amk.eval_cons_pos hl₁ at hmk,
          rw eval_cons_pos hl₁,
          exact ih hmk (succ_lt_succ_iff.mp hk) } } } }
end

-- Since the at-least-one encoding is so simple, we prove it here
section direct_alo

open encoding

def direct_alo : enc_fn V := λ l g, ⟨[l], g⟩

theorem direct_alo_is_wb : is_wb (direct_alo : enc_fn V) :=
begin
  intros l g hdis, simp [direct_alo]
end

theorem direct_alo_encodes_alo : encodes (alk 1) (direct_alo : enc_fn V) :=
begin
  split,
  {
    intros l g hdis τ,
    rw alo_eval_tt_iff_exists_eval_tt,
    split,
    { intro h,
      use τ, split,
      { simp [direct_alo, eval_tt_iff_exists_literal_eval_tt, h] },
      { refl } },
    {
      rintro ⟨σ, hs, hagree_on⟩,
      simp [direct_alo, eval_tt_iff_exists_literal_eval_tt] at hs,
      rcases hs with ⟨lit, hmem, hlit⟩,
      rw ← eval_eq_of_agree_on_of_var_mem hagree_on (mem_vars_of_mem hmem) at hlit,
      exact ⟨lit, hmem, hlit⟩ } },
  { exact direct_alo_is_wb }
end

end direct_alo

end alk


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

! This file was ported from Lean 3 source module data.finset.nat_antidiagonal
! leanprover-community/mathlib commit cc70d9141824ea8982d1562ce009952f2c3ece30
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Finset.Card
import Mathbin.Data.Multiset.NatAntidiagonal

/-!
# Antidiagonals in ℕ × ℕ as finsets

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

This file defines the antidiagonals of ℕ × ℕ as finsets: the `n`-th antidiagonal is the finset of
pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

This refines files `data.list.nat_antidiagonal` and `data.multiset.nat_antidiagonal`.
-/

namespace Finset

namespace Nat

#print Finset.Nat.antidiagonal /-
/-- The antidiagonal of a natural number `n` is
    the finset of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : Finset (ℕ × ℕ) :=
  ⟨Multiset.Nat.antidiagonal n, Multiset.Nat.nodup_antidiagonal n⟩
#align finset.nat.antidiagonal Finset.Nat.antidiagonal
-/

#print Finset.Nat.mem_antidiagonal /-
/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp]
theorem mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} : x ∈ antidiagonal n ↔ x.1 + x.2 = n := by
  rw [antidiagonal, mem_def, Multiset.Nat.mem_antidiagonal]
#align finset.nat.mem_antidiagonal Finset.Nat.mem_antidiagonal
-/

#print Finset.Nat.card_antidiagonal /-
/-- The cardinality of the antidiagonal of `n` is `n + 1`. -/
@[simp]
theorem card_antidiagonal (n : ℕ) : (antidiagonal n).card = n + 1 := by simp [antidiagonal]
#align finset.nat.card_antidiagonal Finset.Nat.card_antidiagonal
-/

#print Finset.Nat.antidiagonal_zero /-
/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp]
theorem antidiagonal_zero : antidiagonal 0 = {(0, 0)} :=
  rfl
#align finset.nat.antidiagonal_zero Finset.Nat.antidiagonal_zero
-/

#print Finset.Nat.antidiagonal_succ /-
theorem antidiagonal_succ (n : ℕ) :
    antidiagonal (n + 1) =
      cons (0, n + 1)
        ((antidiagonal n).map
          (Function.Embedding.prodMap ⟨Nat.succ, Nat.succ_injective⟩ (Function.Embedding.refl _)))
        (by simp) :=
  by
  apply eq_of_veq
  rw [cons_val, map_val]
  · apply Multiset.Nat.antidiagonal_succ
#align finset.nat.antidiagonal_succ Finset.Nat.antidiagonal_succ
-/

#print Finset.Nat.antidiagonal_succ' /-
theorem antidiagonal_succ' (n : ℕ) :
    antidiagonal (n + 1) =
      cons (n + 1, 0)
        ((antidiagonal n).map
          (Function.Embedding.prodMap (Function.Embedding.refl _) ⟨Nat.succ, Nat.succ_injective⟩))
        (by simp) :=
  by
  apply eq_of_veq
  rw [cons_val, map_val]
  exact Multiset.Nat.antidiagonal_succ'
#align finset.nat.antidiagonal_succ' Finset.Nat.antidiagonal_succ'
-/

#print Finset.Nat.antidiagonal_succ_succ' /-
theorem antidiagonal_succ_succ' {n : ℕ} :
    antidiagonal (n + 2) =
      cons (0, n + 2)
        (cons (n + 2, 0)
            ((antidiagonal n).map
              (Function.Embedding.prodMap ⟨Nat.succ, Nat.succ_injective⟩
                ⟨Nat.succ, Nat.succ_injective⟩)) <|
          by simp)
        (by simp) :=
  by
  simp_rw [antidiagonal_succ (n + 1), antidiagonal_succ', Finset.map_cons, map_map]
  rfl
#align finset.nat.antidiagonal_succ_succ' Finset.Nat.antidiagonal_succ_succ'
-/

#print Finset.Nat.map_swap_antidiagonal /-
theorem map_swap_antidiagonal {n : ℕ} :
    (antidiagonal n).map ⟨Prod.swap, Prod.swap_rightInverse.Injective⟩ = antidiagonal n :=
  eq_of_veq <| by simp [antidiagonal, Multiset.Nat.map_swap_antidiagonal]
#align finset.nat.map_swap_antidiagonal Finset.Nat.map_swap_antidiagonal
-/

#print Finset.Nat.antidiagonal_congr /-
/-- A point in the antidiagonal is determined by its first co-ordinate. -/
theorem antidiagonal_congr {n : ℕ} {p q : ℕ × ℕ} (hp : p ∈ antidiagonal n)
    (hq : q ∈ antidiagonal n) : p = q ↔ p.fst = q.fst :=
  by
  refine' ⟨congr_arg Prod.fst, fun h => Prod.ext h ((add_right_inj q.fst).mp _)⟩
  rw [mem_antidiagonal] at hp hq
  rw [hq, ← h, hp]
#align finset.nat.antidiagonal_congr Finset.Nat.antidiagonal_congr
-/

#print Finset.Nat.antidiagonal.fst_le /-
theorem antidiagonal.fst_le {n : ℕ} {kl : ℕ × ℕ} (hlk : kl ∈ antidiagonal n) : kl.1 ≤ n :=
  by
  rw [le_iff_exists_add]
  use kl.2
  rwa [mem_antidiagonal, eq_comm] at hlk
#align finset.nat.antidiagonal.fst_le Finset.Nat.antidiagonal.fst_le
-/

#print Finset.Nat.antidiagonal.snd_le /-
theorem antidiagonal.snd_le {n : ℕ} {kl : ℕ × ℕ} (hlk : kl ∈ antidiagonal n) : kl.2 ≤ n :=
  by
  rw [le_iff_exists_add]
  use kl.1
  rwa [mem_antidiagonal, eq_comm, add_comm] at hlk
#align finset.nat.antidiagonal.snd_le Finset.Nat.antidiagonal.snd_le
-/

/- warning: finset.nat.filter_fst_eq_antidiagonal -> Finset.Nat.filter_fst_eq_antidiagonal is a dubious translation:
lean 3 declaration is
  forall (n : Nat) (m : Nat), Eq.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.filter.{0} (Prod.{0, 0} Nat Nat) (fun (x : Prod.{0, 0} Nat Nat) => Eq.{1} Nat (Prod.fst.{0, 0} Nat Nat x) m) (fun (a : Prod.{0, 0} Nat Nat) => Nat.decidableEq (Prod.fst.{0, 0} Nat Nat a) m) (Finset.Nat.antidiagonal n)) (ite.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (LE.le.{0} Nat Nat.hasLe m n) (Nat.decidableLe m n) (Singleton.singleton.{0, 0} (Prod.{0, 0} Nat Nat) (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.hasSingleton.{0} (Prod.{0, 0} Nat Nat)) (Prod.mk.{0, 0} Nat Nat m (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n m))) (EmptyCollection.emptyCollection.{0} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.hasEmptyc.{0} (Prod.{0, 0} Nat Nat))))
but is expected to have type
  forall (n : Nat) (m : Nat), Eq.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.filter.{0} (Prod.{0, 0} Nat Nat) (fun (x : Prod.{0, 0} Nat Nat) => Eq.{1} Nat (Prod.fst.{0, 0} Nat Nat x) m) (fun (a : Prod.{0, 0} Nat Nat) => instDecidableEqNat (Prod.fst.{0, 0} Nat Nat a) m) (Finset.Nat.antidiagonal n)) (ite.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (LE.le.{0} Nat instLENat m n) (Nat.decLe m n) (Singleton.singleton.{0, 0} (Prod.{0, 0} Nat Nat) (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.instSingletonFinset.{0} (Prod.{0, 0} Nat Nat)) (Prod.mk.{0, 0} Nat Nat m (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n m))) (EmptyCollection.emptyCollection.{0} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.instEmptyCollectionFinset.{0} (Prod.{0, 0} Nat Nat))))
Case conversion may be inaccurate. Consider using '#align finset.nat.filter_fst_eq_antidiagonal Finset.Nat.filter_fst_eq_antidiagonalₓ'. -/
theorem filter_fst_eq_antidiagonal (n m : ℕ) :
    filter (fun x : ℕ × ℕ => x.fst = m) (antidiagonal n) = if m ≤ n then {(m, n - m)} else ∅ :=
  by
  ext ⟨x, y⟩
  simp only [mem_filter, nat.mem_antidiagonal]
  split_ifs with h h
  · simp (config := { contextual := true }) [and_comm', eq_tsub_iff_add_eq_of_le h, add_comm]
  · rw [not_le] at h
    simp only [not_mem_empty, iff_false_iff, not_and]
    exact fun hn => ne_of_lt (lt_of_le_of_lt (le_self_add.trans hn.le) h)
#align finset.nat.filter_fst_eq_antidiagonal Finset.Nat.filter_fst_eq_antidiagonal

/- warning: finset.nat.filter_snd_eq_antidiagonal -> Finset.Nat.filter_snd_eq_antidiagonal is a dubious translation:
lean 3 declaration is
  forall (n : Nat) (m : Nat), Eq.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.filter.{0} (Prod.{0, 0} Nat Nat) (fun (x : Prod.{0, 0} Nat Nat) => Eq.{1} Nat (Prod.snd.{0, 0} Nat Nat x) m) (fun (a : Prod.{0, 0} Nat Nat) => Nat.decidableEq (Prod.snd.{0, 0} Nat Nat a) m) (Finset.Nat.antidiagonal n)) (ite.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (LE.le.{0} Nat Nat.hasLe m n) (Nat.decidableLe m n) (Singleton.singleton.{0, 0} (Prod.{0, 0} Nat Nat) (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.hasSingleton.{0} (Prod.{0, 0} Nat Nat)) (Prod.mk.{0, 0} Nat Nat (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n m) m)) (EmptyCollection.emptyCollection.{0} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.hasEmptyc.{0} (Prod.{0, 0} Nat Nat))))
but is expected to have type
  forall (n : Nat) (m : Nat), Eq.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.filter.{0} (Prod.{0, 0} Nat Nat) (fun (x : Prod.{0, 0} Nat Nat) => Eq.{1} Nat (Prod.snd.{0, 0} Nat Nat x) m) (fun (a : Prod.{0, 0} Nat Nat) => instDecidableEqNat (Prod.snd.{0, 0} Nat Nat a) m) (Finset.Nat.antidiagonal n)) (ite.{1} (Finset.{0} (Prod.{0, 0} Nat Nat)) (LE.le.{0} Nat instLENat m n) (Nat.decLe m n) (Singleton.singleton.{0, 0} (Prod.{0, 0} Nat Nat) (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.instSingletonFinset.{0} (Prod.{0, 0} Nat Nat)) (Prod.mk.{0, 0} Nat Nat (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n m) m)) (EmptyCollection.emptyCollection.{0} (Finset.{0} (Prod.{0, 0} Nat Nat)) (Finset.instEmptyCollectionFinset.{0} (Prod.{0, 0} Nat Nat))))
Case conversion may be inaccurate. Consider using '#align finset.nat.filter_snd_eq_antidiagonal Finset.Nat.filter_snd_eq_antidiagonalₓ'. -/
theorem filter_snd_eq_antidiagonal (n m : ℕ) :
    filter (fun x : ℕ × ℕ => x.snd = m) (antidiagonal n) = if m ≤ n then {(n - m, m)} else ∅ :=
  by
  have : (fun x : ℕ × ℕ => x.snd = m) ∘ Prod.swap = fun x : ℕ × ℕ => x.fst = m :=
    by
    ext
    simp
  rw [← map_swap_antidiagonal]
  simp [filter_map, this, filter_fst_eq_antidiagonal, apply_ite (Finset.map _)]
#align finset.nat.filter_snd_eq_antidiagonal Finset.Nat.filter_snd_eq_antidiagonal

section EquivProd

#print Finset.Nat.sigmaAntidiagonalEquivProd /-
/-- The disjoint union of antidiagonals `Σ (n : ℕ), antidiagonal n` is equivalent to the product
    `ℕ × ℕ`. This is such an equivalence, obtained by mapping `(n, (k, l))` to `(k, l)`. -/
@[simps]
def sigmaAntidiagonalEquivProd : (Σn : ℕ, antidiagonal n) ≃ ℕ × ℕ
    where
  toFun x := x.2
  invFun x := ⟨x.1 + x.2, x, mem_antidiagonal.mpr rfl⟩
  left_inv := by
    rintro ⟨n, ⟨k, l⟩, h⟩
    rw [mem_antidiagonal] at h
    exact Sigma.subtype_ext h rfl
  right_inv x := rfl
#align finset.nat.sigma_antidiagonal_equiv_prod Finset.Nat.sigmaAntidiagonalEquivProd
-/

end EquivProd

end Nat

end Finset


/-
Copyright (c) 2020 Hanting Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Hanting Zhang
-/
import ring_theory.polynomial.basic
import ring_theory.polynomial.symmetric

/-!
# Vieta's Formula

The main result is `vieta.prod_X_add_C_eq_sum_esymm`, which shows that the product of linear terms
`λ + X i` is equal to a linear combination of the symmetric polynomials `esymm σ R j`.

## Implementation Notes:

We first take the viewpoint where the "roots" `X i` are variables. This means we work over
`polynomial (mv_polynomial σ R)`, which enables us to talk about linear combinations of
`esymm σ R j`. We then derive Vieta's formula in `polynomial R` by giving a
valuation from each `X i` to `r i`.

-/

open_locale big_operators polynomial

open finset polynomial fintype

namespace mv_polynomial

variables {R : Type*} [comm_semiring R]
variables (σ : Type*) [fintype σ]

/-- A sum version of Vieta's formula. Viewing `X i` as variables,
the product of linear terms `λ + X i` is equal to a linear combination of
the symmetric polynomials `esymm σ R j`. -/
lemma prod_X_add_C_eq_sum_esymm :
  (∏ i : σ, (polynomial.C (X i) + polynomial.X) : polynomial (mv_polynomial σ R) )=
  ∑ j in range (card σ + 1),
    (polynomial.C (esymm σ R j) * polynomial.X ^ (card σ - j)) :=
begin
  classical,
  rw [prod_add, sum_powerset],
  refine sum_congr begin congr end (λ j hj, _),
  rw [esymm, map_sum, sum_mul],
  refine sum_congr rfl (λ t ht, _),
  have h : (univ \ t).card = card σ - j :=
  by { rw card_sdiff (mem_powerset_len.mp ht).1, congr, exact (mem_powerset_len.mp ht).2 },
  rw [map_prod, prod_const, ← h],
end

/-- A fully expanded sum version of Vieta's formula, evaluated at the roots.
The product of linear terms `X + r i` is equal to `∑ j in range (n + 1), e_j * X ^ (n - j)`,
where `e_j` is the `j`th symmetric polynomial of the constant terms `r i`. -/
lemma prod_X_add_C_eval (r : σ → R) : ∏ i : σ, (polynomial.C (r i) + polynomial.X) =
  ∑ i in range (card σ + 1), (∑ t in powerset_len i (univ : finset σ),
    ∏ i in t, polynomial.C (r i)) * polynomial.X ^ (card σ - i) :=
begin
  classical,
  have h := @prod_X_add_C_eq_sum_esymm _ _ σ _,
  apply_fun (polynomial.map (eval r)) at h,
  rw [polynomial.map_prod, polynomial.map_sum] at h,
  convert h,
  simp only [eval_X, polynomial.map_add, polynomial.map_C, polynomial.map_X, eq_self_iff_true],
  funext,
  simp only [function.funext_iff, esymm, polynomial.map_C, polynomial.map_sum, map_sum,
    polynomial.map_C, polynomial.map_pow, polynomial.map_X, polynomial.map_mul],
  congr,
  funext,
  simp only [eval_prod, eval_X, map_prod],
end

lemma esymm_to_sum (r : σ → R) (j : ℕ) : polynomial.C (eval r (esymm σ R j)) =
  ∑ t in powerset_len j (univ : finset σ), ∏ i in t, polynomial.C (r i) :=
by simp only [esymm, eval_sum, eval_prod, eval_X, map_sum, map_prod]

/-- Vieta's formula for the coefficients of the product of linear terms `X + r i`,
The `k`th coefficient is `∑ t in powerset_len (card σ - k) (univ : finset σ), ∏ i in t, r i`,
i.e. the symmetric polynomial `esymm σ R (card σ - k)` of the constant terms `r i`. -/

end mv_polynomial


import tactic -- hide
open function nat -- hide

/-
## More on `rw`

We have seen that the `rw` tactic replaces occurrences of $a$ with $b$ if we have `h : a = b`.
If we want to replace the occurrences of $b$ into $a$, we can use the fact that `h.symm` is a proof of
$b = a$ (and hence do `rw h.symm`) or use `rw ← h` (type `←` with \l). Try it below.
-/

/-
**Pro tip:** In very rare occasions, we might end up with a goal of the form `a = a`, where
`a` is a very complicated expression. This can be closed with the `refl` tactic (it means
*reflexivity*).
-/

/- Symbol:
← : \l
-/
/- Lemma : no-side-bar
If $a = b + c$ and $a = 3$, then $b + c = 3$.
-/
lemma l2 (a b c : ℕ) (h1 : a = b + c) (h2 : a = 3): b + c = 3:=
begin
  rw ← h1,
  assumption,

end


-- La suma de dos funciones pares es una función par
-- =================================================

import data.real.basic

variables (x y : ℝ)
variables (f g : ℝ → ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la función
--    par : (ℝ → ℝ) → Prop
-- tal que (par f) expresa que f es par.
-- ----------------------------------------------------

def par (f : ℝ → ℝ) : Prop :=
∀ x, f (-x) = f x

-- ----------------------------------------------------
-- Ejercicio 2. Definir la función
--    suma : (ℝ → ℝ) → (ℝ → ℝ) → (ℝ → ℝ)
-- tal que (suma f g) es la suma de las funciones f y g.
-- ----------------------------------------------------

@[simp]
def suma (f g: ℝ → ℝ) : ℝ → ℝ :=
λ x, f x + g x

-- ----------------------------------------------------
-- Ejercicio 3. Demostrar que la suma de funciones
-- pares es par.
-- ----------------------------------------------------

-- 1ª demostración
example :
  par f → par g →  par (suma f g) :=
begin
  intro hf,
  unfold par at hf,
  intro hg,
  unfold par at hg,
  unfold par,
  intro x,
  unfold suma,
  rw hf,
  rw hg,
end

-- 2ª demostración
example :
  par f → par g →  par (suma f g) :=
begin
  intros hf hg x,
  simp [suma],
  rw [hf, hg],
end

-- 3ª demostración
example :
  par f → par g →  par (suma f g) :=
begin
  intros hf hg x,
  unfold suma,
  rw [hf, hg],
end

-- 4ª demostración
example :
  par f → par g →  par (suma f g) :=
begin
  intros hf hg x,
  calc (f + g) (-x)
       = f (-x) + g (-x) : rfl
   ... = f x + g (-x)    : by rw hf
   ... = f x + g x       : by rw hg
   ... = (f + g) x       : rfl
end

-- 5ª demostración
example :
  par f → par g →  par (suma f g) :=
begin
  intros hf hg x,
  calc (f + g) (-x)
       = f (-x) + g (-x) : rfl
   ... = f x + g x       : by rw [hf, hg]
end


/- LoVe Homework 3: Structured Proofs and Proof Terms -/

import .love02_tactical_proofs_exercise_sheet

namespace LoVe

/- Question 2: Logic Puzzles -/

/- 2.2 (**optional**). Prove the same lemma again, this time by providing a
proof term.

Hint: There is an easy way. -/

lemma weak_peirce₂ :
  ∀a b : Prop, ((((a → b) → a) → a) → b) → b :=
λ(a b : Prop) (habaab : (((a → b) → a) → a) → b),
  habaab (λ(habaa : (a → b) → a),
    habaa (λ(ha : a), habaab (λ(haba : (a → b) → a), ha)))

/- The easy way is `#print weak_peirce`. There is an even easier way: to use
`weak_peirce` as the proof of `weak_peirce₂`. -/

/- 2.3 (**optional**). Prove the same lemma again, this time by providing a
structured proof. -/

lemma weak_peirce₃ :
  ∀a b : Prop, ((((a → b) → a) → a) → b) → b :=
assume (a b : Prop) (habaab : (((a → b) → a) → a) → b),
show b, from habaab
  (assume (habaa : (a → b) → a),
   show a, from habaa
     (assume (ha : a),
      show b, from habaab
        (assume (haba : (a → b) → a),
         show a, from ha)))

end LoVe


open classical
attribute [instance] classical.prop_decidable

namespace logic

lemma of_contrapositive {P Q : Prop} :
(¬ Q → ¬ P) → (P → Q) :=
begin
intros H hp,
by_cases hq : Q,
assumption,
apply absurd hp,
apply H,
assumption,
end

lemma of_cases {P Q : Prop} : (P → Q) → (¬ P → Q) → Q :=
begin
intros ht hf,
by_cases h : P,
apply ht,
assumption,
apply hf,
assumption,
end

lemma resolve_left {P Q : Prop} :
(¬ Q → P) → P ∨ Q :=
begin
intro H,
by_cases hq : Q,
right,
assumption,
left,
apply H,
assumption,
end

lemma double_negation {P : Prop} : P → ¬ ¬ P :=
begin
intros h f,
apply f,
assumption,
end

lemma resolve_right {P Q : Prop} :
(¬ P → Q) → P ∨ Q :=
begin
intro H,
by_cases hp : P,
left,
assumption,
right,
apply H,
assumption,
end

lemma demorgan_and {P Q : Prop} : ¬ P ∨ ¬ Q → ¬ (P ∧ Q) :=
begin
intros K h,
cases h with hp hq,
cases K,
apply K,
assumption,
apply K,
assumption,
end

lemma demorgan_exists {α : Sort*} {P : α → Prop} : (∀ x, ¬ P x) → ¬ (∃ x, P x) :=
begin
intros K h,
cases h with x hx,
apply K x,
assumption,
end

lemma demorgan_forall {α : Sort*} {P : α → Prop} : (∃ x, ¬ P x) → ¬ (∀ x, P x) :=
begin
intros K h,
cases K with x kx,
apply kx,
apply h,
end

lemma demorgan_or {P Q : Prop} : ¬ P ∧ ¬ Q → ¬ (P ∨ Q) :=
begin
intros K h,
cases K with Kp Kq,
cases h,
apply Kp,
assumption,
apply Kq,
assumption,
end

lemma demorgan_and' {P Q : Prop} : ¬ (P ∧ Q) → ¬ P ∨ ¬ Q :=
begin
intro H,
by_cases hp : P,
by_cases hq : Q,
apply absurd _ H,
split; assumption,
right, assumption,
left, assumption,
end

lemma demorgan_or' {P Q : Prop} : ¬ (P ∨ Q) → ¬ P ∧ ¬ Q :=
begin
intro H,
by_cases hp : P,
apply absurd _ H,
left, assumption,
by_cases hq : Q,
apply absurd _ H,
right, assumption,
split,
assumption,
assumption,
end

lemma demorgan_exists' {α : Sort*} {P : α → Prop} : ¬ (∃ x, P x) → (∀ x, ¬ P x) :=
begin
intros H x hx,
apply H,
existsi x,
assumption,
end

lemma demorgan_forall' {α : Sort*} {P : α → Prop} : ¬ (∀ x, P x) → (∃ x, ¬ P x) :=
begin
intros H,
by_cases H' : ∃ x, ¬ P x,
assumption,
apply absurd _ H,
intro x,
by_cases P x,
assumption,
apply absurd _ H',
existsi x,
assumption,
end

end logic

namespace tactic

meta def interactive.resolve_left : tactic unit := `[apply logic.resolve_left]

meta def interactive.resolve_right : tactic unit := `[apply logic.resolve_right]

meta def interactive.by_contrapositive : tactic unit :=
`[apply logic.of_contrapositive]

meta def interactive.by_demorgan : tactic unit :=
`[ apply logic.double_negation
  <|> apply logic.demorgan_and
  <|> apply logic.demorgan_or
  <|> apply logic.demorgan_exists
  <|> apply logic.demorgan_forall]

--meta def interactive.by_demorgan : tactic unit :=

end tactic


-- https://www.codewars.com/kata/5e889a3c0f55ed0032f1879f/train/lean

/-
  Incidence geometry in Lean, referenced from
  https://sites.math.washington.edu/~lee/Courses/444-2009/theorems-incidence.pdf
  and the lecture notes for MATH 4221 (not publicly available on the Internet)
-/

-- Undefined terms and axioms of incidence geometry
-- In incidence geometry, there are 3 undefined terms:
-- - Point
-- - Line
-- - "Incident with"/"lies on", e.g. point P is incident with / lies on line l
-- And there are 3 axioms:
-- - I₁: For every pair of distinct points P and Q there is exactly 1 line l such
--   that P and Q lie on l
-- - I₂: For every line l there exist at least two distinct points P and Q such
--   that both P and Q lie on l
-- - I₃: There exist three points that do not all lie on any one line
-- Note that the three points in I₃ are implicitly assumed to be distinct, because
-- otherwise one could construct a trivial model with only one point and no lines
-- that trivially satisfies the axioms, which is uninteresting, and some of the
-- theorems stated below do not hold in the trivial model
import tactic

class incidence (point line : Type) (incident_with : point → line → Prop) :=
  (I₁ : ∀ P Q, P ≠ Q → ∃! l, incident_with P l ∧ incident_with Q l)
  (I₂ : ∀ l, ∃ P Q, P ≠ Q ∧ incident_with P l ∧ incident_with Q l)
  (I₃ : ∃ P Q R, P ≠ Q ∧ Q ≠ R ∧ P ≠ R ∧
    ∀ l, ¬(incident_with P l ∧ incident_with Q l ∧ incident_with R l))

theorem thm_3p6p2 (point line : Type) (incident_with : point → line → Prop)
  [incidence point line incident_with] (l : line) :
  ∃ P, ¬incident_with P l :=
begin
-- from _inst_1.I₃ extract three points P, Q, R,
-- the proofs of distinctness hPQ, hQR, hPR,
-- and the proof of incidence hl of P, Q, R on l
rcases _inst_1.I₃ with ⟨ P, Q, R, hPQ, hQR, hPR, hl⟩,
specialize hl l,
push_neg at hl,

by_cases hpl: (incident_with P l),
  by_cases hql: (incident_with Q l),

    -- both P and Q lie on l
    show ∃ (P : point), ¬incident_with P l,
      use R,
      exact hl hpl hql,

    -- P lies on l but Q does not lie on l
    show ∃ (P : point), ¬incident_with P l,
      use Q,

    -- P does not lie on l
    show ∃ (P : point), ¬incident_with P l,
      use P,
end


import data.int.basic
import data.nat.prime

/-
Correctness 90/90
Style: 10/10
Good work Priya!
-/

/- This assignment is due by 11:59pm on Friday, March 10th 2023. -/

/-
EXERCISE 1. Using the definition `mypow a n`, which is supposed to define
exponentiation `a^n`, use induction to prove the theorem below.

Hint: you can use `nat.add_succ` to unfold the defition of `m + n.succ`.
-/

section
variables {α : Type*} [comm_monoid α]

def mypow : α → ℕ → α
| a 0       := 1
| a (n + 1) := a * (mypow a n)

#eval mypow 3 5

theorem mypow_zero (a : α) : mypow a 0 = 1 := rfl

theorem mypow_succ (a : α) (n : ℕ) : mypow a (n + 1) = a * mypow a n := rfl

-- Exercise 1 [10pts].
theorem mypow_add (a : α) (m n : ℕ) : mypow a (m + n) = mypow a m * mypow a n :=
begin
  induction n with n ih,
  { -- n = 0
    simp [mypow] },
  { -- inductive step
    simp [nat.add_succ, mypow, ih],
    repeat { rw ←mul_assoc }, -- break up all parentheses
    simp [mul_comm] }
end

end

/-
EXERCISE 2.

In class, we have used ordinary induction on the natural numbers,
which allows you to prove `p n` for an arbitrary natural number
`n` by proving `p 0` and `∀ m, p m → p m.succ`.

It is often more useful to the principle of *complete induction*
or *strong induction*. This is found in the library under the
name `nat.strong_induction_on`, but the exercise below asks you
to prove it independently, using ordinary induction on the natural numbers.
The principle is stated in a form that the induction tactic
can use it, as illustrated in exercise 3.

The trick is to prove the stronger claim `∀ n, ∀ m < n, p m` by
induction on the natural numbers. The `suffices` step in the proof
shows that this suffices to establish `p n` for the *particular* `n` in
the context. Once we have done that, we throw away the particular `n`,
and focus on proving the stronger claim by induction.
-/

section

-- Exercise 2 [17pts].
theorem complete_induction_on {p : ℕ → Prop} (n : ℕ)
  (h : ∀ n, (∀ m < n, p m) → p n) : p n :=
begin
  suffices : ∀ n, ∀ m < n, p m,
  { apply h,
    apply this },
  clear n,
  intro n,
  induction n with n ih,
  { -- n = 0
    simp },
  { -- inductive step
    intros m mlen,
    rw nat.lt_succ_iff at mlen,
    apply h,
    intros l lltm,
    apply ih,
    exact lt_of_lt_of_le lltm mlen }
end

end

/-
EXERCISE 3.

In this exercise, we use the principle of strong induction to show that
every natural number greater than or equal to two has a prime divisor.

You can use the lemma `exists_lt_dvd_of_not_prime`. After the boilerplate
that we have set up for you, you should formalize the following argument:
if `n` is prime, we are done.  If `n` is not prime, the lemma tells us that
there it has a nontrivial divisor `m < n`, and we can apply the induction
hypothesis to that.
-/

-- This follows straightforwardly from the definition of `nat.prime`.
lemma exists_lt_dvd_of_not_prime {n : nat} (h : ¬ nat.prime n) (h' : 2 ≤ n) :
  ∃ m, 2 ≤ m ∧ m < n ∧ m ∣ n :=
begin
  simp [nat.prime_def_lt'] at h,
  exact h h'
end

-- Exercise 3 [18pts].
theorem exists_prime_dvd (n : ℕ) : 2 ≤ n → ∃ p, nat.prime p ∧ p ∣ n :=
begin
  induction n using complete_induction_on with n ih,
  dsimp at ih,
  intro nle,
  by_cases hn : nat.prime n,
  { -- n prime
    use n,
    simp [hn] },
  { -- n not prime
    -- m is its nontrivial divisor
    rcases exists_lt_dvd_of_not_prime hn nle with ⟨m, hm, mltn, mdvdn⟩,
    -- apply inductive hypothesis to that
    specialize ih m mltn hm,
    rcases ih with ⟨p, hp, pdvdm⟩,
    exact ⟨p, hp, dvd_trans pdvdm mdvdn⟩ }
end

/-
EXERCISE 4.

Finally, in this exercise, we define the structure of a `quasigroup`,
show that the integers with subtraction form an instance, and prove
some basic properties.

You can find the definition of a quasigroup here:

  https://en.wikipedia.org/wiki/Quasigroup

We'll use the notation `ldiv a b` for left division (on Wikipedia, `a \ b`),
and we'll use `rdiv a b` for right division (on Wikipedia, `a / b`).

(Instantiating the integers as a quasigroup is dangerous, because it
redefines the notation of multiplication to mean substraction. Such
a thing could destroy the understanding of mathematics for a generation
of elementary school students, so please make sure your git repositories
stay private!)
-/

-- Exercise 4a [10pts].
/-
First, fill in the remaining axioms. E.g. the first should say,
"for any `a`, `b` and `x`, if `x` satisfies the defining equation for `a \ b`
(that is, the cancellation law), then it is equal to `a \ b`."
-/

class quasigroup (α : Type*) extends has_mul α :=
(ldiv : α → α → α)
(rdiv : α → α → α)
(mul_ldiv_cancel : ∀ a b, a * ldiv a b = b)
(rdiv_mul_cancel : ∀ a b, rdiv a b * b = a)
(ldiv_unique : ∀ a b x, a * x = b → x = ldiv a b)
(rdiv_unique : ∀ a b x, x * b = a → x = rdiv a b)

-- Exercise 4b [15pts].
/-
Next, show that the integers with subtraction are an instance. You will
have to figure out the right definitions of `ldiv` and `rdiv`. For
example, if you decide `ldiv a b` should be `a * b`, write
`ldiv := λ a b, a * b`.

Note: Be sure to write this out on paper first, and check the identities
as you see them wikipedia.  This will make the coding much easier, and
help avoid you trying to prove something that is impossible.

Note that in goals within the instance definition, you might see "multiplication"
which is really integer subtraction, because that's we defined it as! To check
which one it really is, you can click on the `*` operation in the infoview and look
for something like `{mul := int.sub}`.

Also, the `show` tactic can sometimes be used to unfold definitions. For example
on the goal `⊢ a * b = stuff`, `show a - b = stuff` should work.
-/

instance : quasigroup ℤ :=
{ mul := int.sub,
  ldiv := λ a b, a - b,
  rdiv := λ a b, a + b,
  mul_ldiv_cancel :=
    begin
      repeat { simp [int.sub] },
    end,
  rdiv_mul_cancel :=
    begin
      simp [int.sub],
    end,
  ldiv_unique :=
    begin
      dsimp only [int.sub],
      intros a b x h,
      rw ←neg_sub b a,
      apply eq_neg_of_eq_neg,
      apply eq.symm,
      exact eq_sub_of_add_eq' h,
    end,
  rdiv_unique :=
    begin
      dsimp only [int.sub],
      intros a b x h,
      exact eq_add_of_add_neg_eq h,
    end }

/- Finally, prove that some identities hold in *any* quasigroup. -/

namespace quasigroup
variables {α : Type*} [quasigroup α]

/-
(mul_ldiv_cancel : ∀ a b, a * ldiv a b = b)
(rdiv_mul_cancel : ∀ a b, rdiv a b * b = a)
(ldiv_unique : ∀ a b x, a * x = b → x = ldiv a b)
(rdiv_unique : ∀ a b x, x * b = a → x = rdiv a b)
-/

-- Exercise 4c [5pts].
theorem eq_ldiv_mul_self (y x : α) : y = ldiv x (x * y) :=
begin
  apply ldiv_unique,
  simp,
end

-- Exercise 4d [5pts].
theorem eq_mul_rdiv_self (y x : α) : y = rdiv (y * x) x :=
begin
  apply rdiv_unique,
  simp,
end

-- Exercise 4e [10pts].
theorem left_cancel (a b c : α) (h : a * b = a * c) : b = c :=
begin
  rw (ldiv_unique a (a * c) b) h,
  rw ←eq_ldiv_mul_self,
end

-- Exercise 4f [10pts].
theorem right_cancel (a b c : α) (h : a * b = c * b) : a = c :=
begin
  rw (rdiv_unique (c * b) b a) h,
  rw ←eq_mul_rdiv_self,
end

end quasigroup


/- A toy dialect with basic tensor computations. -/

import MLIRSemantics.Types
import MLIRSemantics.Util.List

/-
### Tensor reshaping operation

This operation can reshape (retype) tensors with fully-known dimensions,
provided that the number of elements doesn't change.
-/

def reshape {α} {D: DimList} (D': DimList)
    (H: D.known) (H': D'.known) (Hprod: D'.prod = D.prod):
    RankedTensor α D → RankedTensor α D' :=
  fun t =>
    { shape       := D'.project,
      data        := t.data
      h_refines   := dim_known_project_refines H',
      h_data_size := by rw [t.h_data_size, dim_known_prod D' H', Hprod]
                        rw [dim_known_prod_refines H]
                        apply t.h_refines }

theorem reshape_reshape {α} {D: DimList} (D₁ D₂: DimList)
    (H: D.known) (H₁: D₁.known) (H₂: D₂.known)
    (Hprod₁: D₁.prod = D.prod) (Hprod₂: D₂.prod = D₁.prod)
    (t: RankedTensor α D):
      reshape D₂ H₁ H₂ Hprod₂ (reshape D₁ H H₁ Hprod₁ t) =
      reshape D₂ H H₂ (Eq.trans Hprod₂ Hprod₁) t :=
  rfl

theorem reshape_self {α} D H₁ H₂ Hprod (t: RankedTensor α D):
    reshape D H₁ H₂ Hprod t = t := by
  simp [reshape, dim_known_project_eq H₁ t.h_refines]

/-
### Tensor transposition operation

This operation shuffles the elements of the underlying data without changing
its size. To keep this clean it's beneficial to separate the dimension logic
from the index manipulation of the transposition itself.
-/

def transpose_remap (n m: Nat): Nat → Nat :=
  fun i => m * (i % n) + (i / n)

theorem transpose_remap_bound (n m):
    ∀ i, i < n * m → transpose_remap n m i < n * m := by
  intro i h
  simp [transpose_remap]
  sorry /- m*(≤ n-1)+(< m) -/

theorem transpose_remap_involutive (n m):
    ∀i, transpose_remap m n (transpose_remap n m i) = i := by
  simp [transpose_remap, Function.comp]; intro i
  sorry /- = (i/n)*n + i%n -/

@[inline]
def Matrix α n m :=
  RankedTensor α [MLIR.AST.Dimension.Known n, MLIR.AST.Dimension.Known m]

def transpose {α n m} (t: Matrix α n m): Matrix α m n :=
  { shape := [m, n],
    data := List.remap t.data (transpose_remap n m)
        (by intro i h
            rw [t.h_data_size, dim_known_prod_refines _ t.h_refines] at * <;>
            simp at *
            apply transpose_remap_bound; assumption),
    h_refines := by simp,
    h_data_size := by
      simp [shape_prod, List.foldr];
      rw [t.h_data_size, dim_known_prod_refines _ t.h_refines] <;>
      simp [Nat.mul_comm] }

theorem Function.comp_assoc {α β γ δ} (f: α → β) (g: β → γ) (h: γ → δ):
    (h ∘ g) ∘ f = h ∘ (g ∘ f) :=
  by funext x; simp

theorem transpose_involutive {α n m}:
    ∀ (t: Matrix α n m), transpose (transpose t) = t := by
  intro t;
  simp [transpose]
  apply RankedTensor.eq_of_fields_eq <;> simp
  . rw [←dim_known_project_eq _ t.h_refines] <;> simp
  . simp [List.remap_remap]
    apply List.extF <;> simp
    simp [transpose_remap_involutive]


/-
Exercise 2.
What is the contrapositive of ¬ Q → ¬ P
-I am suggesting P → Q is the contrapositive of ¬ Q → ¬ P. Is this correct?
Prove that it is correct if you agree with me, otherwise change P → Q into what ever your heart desires!

Here are some basic tactics that might help : http://wwwf.imperial.ac.uk/~buzzard/xena/html/source/tactics/guide.html
-/

example (P Q : Prop) : (¬ Q → ¬ P) → (P → Q) :=
begin
    sorry -- Replace the sorry with your proof!
end


import algebra.ring

example {α : Type*} [ring α] (a b c : α) :
  a * 0 + 0 * b + c * 0 + 0 * a = 0 :=
begin
  rw [mul_zero, mul_zero, zero_mul, zero_mul],
  repeat { rw add_zero },
end

example {α : Type*} [group α] {a b : α} (h : a * b = 1) :
a⁻¹ = b :=
by rw [←(mul_one a⁻¹), ← h, inv_mul_cancel_left]


-- ---------------------------------------------------------------------
-- Ejercicio. Sean a, b y c números reales. Demostrar que
--    min (min a b) c = min a (min b c)
-- ----------------------------------------------------------------------

import data.real.basic tactic

variables a b c : ℝ

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

-- Se usará el siguiente lema auxilar.
lemma aux1 : min (min a b) c ≤ min a (min b c) :=
begin
  apply le_min,
  { show min (min a b) c ≤ a,
      calc min (min a b) c
           ≤ min a b : min_le_left (min a b) c
       ... ≤ a       : min_le_left a b },
  { show min (min a b) c ≤ min b c,
    apply le_min,
    { show min (min a b) c ≤ b,
        calc min (min a b) c
             ≤ min a b  : min_le_left (min a b) c
         ... ≤ b        : min_le_right a b },
    { show min (min a b) c ≤ c,
      { by exact min_le_right (min a b) c }}},
end

example :
  min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { show min (min a b) c ≤ min a (min b c),
      by exact aux1 a b c },
  { show min a (min b c) ≤ min (min a b) c,
      calc min a (min b c)
           = min (min b c) a : by apply min_comm
       ... = min (min c b) a : by {congr' 1, apply min_comm}
       ... ≤ min c (min b a) : by apply aux1
       ... = min c (min a b) : by {congr' 1, apply min_comm}
       ... = min (min a b) c : by apply min_comm },
end

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

-- Se usará el siguiente lema auxilar.
lemma aux2 : min (min a b) c ≤ min a (min b c) :=
begin
  apply le_min,
  { calc min (min a b) c
         ≤ min a b : by simp [min_le_left]
     ... ≤ a       : by simp [min_le_left] },
  { apply le_min,
    { calc min (min a b) c
           ≤ min a b  : by simp [min_le_left]
       ... ≤ b        : by simp [min_le_right] },
    { simp [min_le_right] }},
end

example : min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { by exact aux2 a b c },
  { calc min a (min b c)
         = min (min b c) a : by simp [min_comm]
     ... = min (min c b) a : by simp [min_comm]
     ... ≤ min c (min b a) : by simp [aux2]
     ... = min c (min a b) : by simp [min_comm]
     ... = min (min a b) c : by simp [min_comm] },
end

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

example : min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { by exact aux2 a b c },
  { calc min a (min b c)
         = min (min c b) a : by simp [min_comm]
     ... ≤ min c (min b a) : by simp [aux2]
     ... = min (min a b) c : by simp [min_comm] },
end


import linear_algebra

variables (R : Type*) (M : Type*)

/--
An abbreviation for linear combinations.
By definition, a linear combination is a list of elements of type R × M
-/
abbreviation lincomb := list (R × M)

namespace lincomb

variables {R M}

/--
consts L is the list of constants appearing in the linear combination L.
-/
def consts : lincomb R M → list R := list.map prod.fst

/--
vects L is the list of vectors (or module elements) appearing in the linear combination L.
-/
def vects : lincomb R M → list M := list.map prod.snd

@[simp]
lemma vects_cons {t : R × M} {L : lincomb R M} : vects (t :: L)  = t.2 :: vects L := by refl

variable [semiring R]

/--
lmul c L is the linear combination obtained by multiplying all the constants of L by c (on the left).
-/
def lmul (c : R) : lincomb R M → lincomb R M
| [] := []
| ((a,m)::ls) := (c * a,m) :: (lmul ls)

@[simp]
lemma lmul_cons {c : R} {l : R × M} {L : lincomb R M} :
  lmul c (l :: L) = (c * l.1, l.2) :: (lmul c L) := by {cases l, refl}

@[simp]
lemma vects_lmul {c : R} {L : lincomb R M} : vects (lmul c L) = vects L :=
begin
  induction L with l ls hls,
  { simpa },
  { simpa [vects, lmul] }
end

variables [add_comm_monoid M] [semimodule R M]

/--
The evaluation of a linear combination in the module.
-/
def eval : lincomb R M → M
| [] := 0
| ((r,m) :: rest) := r • m + eval rest

@[simp]
lemma eval_nil : eval ([] : lincomb R M) = 0 := by refl

@[simp]
lemma eval_cons {l : R × M} {ls : lincomb R M} : eval (l :: ls) = l.1 • l.2 + eval ls :=
  by {cases l, refl}

@[simp]
lemma eval_add {L1 L2 : lincomb R M} : eval (L1 ++ L2) = eval L1 + eval L2 :=
begin
  induction L1 with l ls hls,
  { simp },
  { have : l :: ls ++ L2 = l :: (ls ++ L2), by refl,
    rw [this, eval_cons],
    finish, }
end

@[simp]
lemma eval_lmul {c : R} {L : lincomb R M} : eval (lmul c L) = c • (eval L) :=
begin
  induction L with l ls hls,
  { simpa only [eval_nil, smul_zero] },
  { rwa [lmul_cons,eval_cons,hls,eval_cons,mul_smul,smul_add] }
end

open submodule
open_locale classical

theorem mem_span {L : lincomb R M} : eval L ∈ (span R (↑(list.to_finset (vects L)) : set M)) :=
begin
  induction L with l ls hls,
  { simp, },
  { cases l with c v,
    set vs := vects ls,
    have : (vects ((c,v) :: ls)).to_finset = insert v vs.to_finset, by simp,
    rw [this, eval_cons],
    set H := span R (↑(insert v vs.to_finset) : set M),
    apply add_mem' H,
    { simp only [],
      apply smul_mem' H,
      apply subset_span,
      simp, },
    { suffices : eval ls ∈ span R (↑(vs.to_finset) : set M),
        by refine span_mono _ this; simp,
      assumption, }}
end

end lincomb

variable {M}

variables [semiring R] [add_comm_monoid M] [semimodule R M]

open lincomb

/--
The span of a set S defined in terms of linear combinations (using the above definition).
-/
def lcspan (S : set M) : submodule R M :=
begin
  refine_struct {
    carrier := { m | ∃ L : list (R × M), (eval L) = m ∧ (∀ {l}, l ∈ vects L → l ∈ S)}
  },
  { refine ⟨[],_,_⟩,
    { refl },
    { intros _ hl, exfalso, exact hl }},
  { rintros m n ⟨A,hA1,hA2⟩ ⟨B, hB1, hB2⟩,
    use A ++ B,
    split,
    { rwa [eval_add, hA1,hB1] },
    { intros l hl,
      unfold vects at hl,
      rw [list.map_append prod.snd, list.mem_append] at hl,
      cases hl, {apply hA2, assumption}, {apply hB2, assumption}}},
  { rintros c m ⟨L,hL1,hL2⟩,
    refine ⟨lmul c L,_,_⟩,
    { rwa [eval_lmul,hL1] },
    { rwa vects_lmul }, }
end

namespace lcspan

open submodule

/--
lcspan S agrees with span (which is defined as the infemum over all submodules containing S)
-/
theorem lcspan_eq_span {S : set M} : lcspan R S = span R S :=
begin
  ext m,
  split,
  { intro hm,
    rcases hm with ⟨L,rfl,hL2⟩,
    induction L with l ls hls,
    { apply zero_mem' (span R S) },
    { rw eval_cons,
      apply add_mem' (span R S),
      { apply smul_mem' (span R S), apply subset_span, apply hL2,
        rw vects_cons, simp, },
      { apply hls, intros u hu, apply hL2, rw vects_cons,
        exact list.mem_cons_of_mem l.snd hu, }}},
  { intro hm,
    have claim : lcspan R S ∈ { U : submodule R M | S ⊆ ↑U },
    { intros s hs,
      refine ⟨[(1,s)],_,_⟩,
      { simp, },
      { intros l hl,
        simp only [list.mem_cons_iff, vects_cons] at hl,
        rcases hl with ⟨ rfl,hl⟩,
        { assumption },
        { exfalso, exact hl, }}},
    { apply Inf_le claim,
      assumption, }
  }
end

end lcspan


import data.nat.prime
import algebra.big_operators
import tactic

open_locale big_operators

theorem two_le {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  by_contra' h,
  interval_cases m; contradiction,
end

example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  cases m, contradiction,
  cases m, contradiction,
  repeat {apply nat.succ_le_succ},
  apply zero_le,
end

example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  by_contra' h,
  revert m h h0 h1,
  dec_trivial,
end

example {m : ℕ} (h : m < 2) : m = 0 ∨ m = 1 :=
  by dec_trivial!

example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
  by omega

theorem exists_prime_factor {n : ℕ} (h : 2 ≤ n) :
  ∃ p : ℕ, p.prime ∧ p ∣ n :=
begin
  by_cases np : n.prime,
  { use [n, np],},
  { induction n using nat.strong_induction_on with n ih,
    dsimp at ih,
    rw nat.prime_def_lt at np,
    push_neg at np,
    rcases np h with ⟨m, mltn, mdvdn, mne1⟩,
    have : m ≠ 0,
      intro mz,
      rw [mz, zero_dvd_iff] at mdvdn,
      linarith,
    have mgt2 : 2 ≤ m,
      from two_le this mne1,
    by_cases mp : m.prime,
    { use [m, mp, mdvdn],},
    { rcases ih m mltn mgt2 mp with ⟨k, kp, kdvdm⟩,
      use [k, kp, dvd_trans kdvdm mdvdn],},}
end

theorem primes_infinite : ∀ n, ∃ p > n, nat.prime p :=
begin
  intro n,
  have : 2 ≤ nat.factorial (n + 1) + 1,
    apply two_le,
    apply ne_of_gt, linarith,
    apply ne_of_gt, apply lt_add_of_pos_left, apply nat.factorial_pos,

  rcases exists_prime_factor this with ⟨p, pp, pdvd⟩,
  refine ⟨p, _, pp⟩,
  show p > n,
  by_contra' ple,
  have : p ∣ nat.factorial (n + 1),
    apply nat.dvd_factorial,
    show 0 < p, from nat.prime.pos pp,
    show p ≤ n + 1, apply le_add_of_le_of_nonneg ple, norm_num,

  have : p ∣ 1,
    convert nat.dvd_sub' pdvd this,
    simp,

  show false,
    from nat.prime.not_dvd_one pp this,
end

open finset
section

variables {α : Type*} [decidable_eq α] (r s t : finset α)

example : r ∩ (s ∪ t) ⊆ (r ∩ s) ∪ (r ∩ t) :=
begin
  rw subset_iff,
  intro x,
  rw [mem_inter, mem_union, mem_union, mem_inter, mem_inter],
  tauto,
end

example : r ∩ (s ∪ t) ⊆ (r ∩ s) ∪ (r ∩ t) :=
by { simp [subset_iff], intro x, tauto }

example : (r ∪ s) ∩ (r ∪ t) = r ∪ (s ∩ t) :=
by { ext x, simp, tauto,}

example : (r \ s \ t) = r \ (s ∪ t) :=
by { ext x, simp, tauto,}

end

example (s : finset ℕ) (n : ℕ) (h : n ∈ s) : n ∣ (∏ i in s, i) :=
  finset.dvd_prod_of_mem _ h

theorem nat.prime.eq_of_dvd_of_prime {p q : ℕ} (prime_p : p.prime) (prime_q : q.prime)
  (h : p ∣ q) : p = q :=
begin
  cases nat.prime.eq_one_or_self_of_dvd prime_q p h with h' h',
  from absurd h' (nat.prime.ne_one prime_p),
  from h',
end

theorem mem_of_dvd_prod_primes {s : finset ℕ} {p : ℕ} (prime_p : p.prime) :
  (∀ n ∈ s, nat.prime n) → (p ∣ ∏ n in s, n) → p ∈ s :=
begin
  intros hp hd,
  induction s using finset.induction_on with a s ans ih,
  { simp at hd,
    linarith [prime_p.two_le],},
  { simp [prod_insert ans, prime_p.dvd_mul] at hp hd,
    rw mem_insert,
    cases hd,
    { left, apply nat.prime.eq_of_dvd_of_prime,
      from prime_p, from hp.1, from hd,},
    { right, apply ih,
      from hp.2, from hd,}}
end

example (s : finset ℕ) (x : ℕ) : x ∈ s.filter nat.prime ↔ x ∈ s ∧ x.prime :=
mem_filter

theorem primes_infinite' : ∀ (s : finset nat), ∃ p, nat.prime p ∧ p ∉ s :=
begin
  intro s,
  by_contra' h,
  set s' := s.filter nat.prime with s'_def,
  have mem_s' : ∀ {n : ℕ}, n ∈ s' ↔ n.prime,
    intro n,
    simp [s'_def],
    apply h,

  have : 2 ≤ (∏ i in s', i) + 1,
     apply nat.succ_le_succ,
     apply nat.succ_le_of_lt,
     apply prod_pos,
     intros n np,
     rw mem_s' at np,
     from nat.prime.pos np,

  rcases exists_prime_factor this with ⟨p, pp, pdvd⟩,

  have : p ∣ (∏ i in s', i),
    apply dvd_prod_of_mem,
    rw mem_s', from pp,

  have : p ∣ 1,
    convert nat.dvd_sub' pdvd this,
    simp,

  show false,
    from nat.prime.not_dvd_one pp this,

end

theorem bounded_of_ex_finset (Q : ℕ → Prop) :
  (∃ s : finset ℕ, ∀ k, Q k → k ∈ s) → (∃ n, ∀ k, Q k → k < n) :=
begin
  rintros ⟨s, hs⟩,
  use s.sup id + 1,
  intros k Qk,
  apply nat.lt_succ_of_le,
  show id k ≤ s.sup id,
  apply le_sup,
  from hs k Qk,
end

theorem ex_finset_of_bounded (Q : ℕ → Prop) [decidable_pred Q] :
  (∃ n, ∀ k, Q k → k ≤ n) → (∃ s : finset ℕ, ∀ k, Q k ↔ k ∈ s) :=
begin
  rintros ⟨n, hn⟩,
  use (range (n + 1)).filter Q,
  intro k,
  split,
  { intro hk,
    simp [nat.lt_succ_iff],
    from ⟨hn k hk, hk⟩,},
  { simp,}
end

example (n : ℕ) : (4 * n + 3) % 4 = 3 :=
  by { rw [add_comm, nat.add_mul_mod_self_left], norm_num}

theorem mod_4_eq_3_or_mod_4_eq_3 {m n : ℕ} (h : m * n % 4 = 3) :
  m % 4 = 3 ∨ n % 4 = 3 :=
begin
  revert h,
  rw [nat.mul_mod],
  have : m % 4 < 4 := nat.mod_lt m (by norm_num),
  interval_cases  m % 4 with hm; simp [hm],
  have : n % 4 < 4 := nat.mod_lt n (by norm_num),
  interval_cases n % 4 with hn; simp [hn]; norm_num,
end

theorem two_le_of_mod_4_eq_3 {n : ℕ} (h : n % 4 = 3) : 2 ≤ n :=
  by apply two_le; { intro neq, rw neq at h, norm_num at h}

theorem aux {m n : ℕ} (h₀ : m ∣ n) (h₁ : 2 ≤ m) (h₂ : m < n) :
  (n / m) ∣ n ∧ (n / m) < n :=
begin
  have : 1 < m , apply lt_of_lt_of_le,
    from (by norm_num : 1 < 2), from h₁,
  split,
  from nat.div_dvd_of_dvd h₀,
  apply nat.div_lt_self,
  apply lt_trans,
  show 0 < 1, norm_num,
  from lt_trans this h₂,
  from this,
end

theorem exists_prime_factor_mod_4_eq_3 {n: ℕ} (h : n % 4 = 3) :
  ∃ p : ℕ, p.prime ∧ p ∣ n ∧ p % 4 = 3 :=
begin
  by_cases np : n.prime,
  { use [n, np, dvd_rfl, h],},

  induction n using nat.strong_induction_on with n ih,
  dsimp at ih,
  rw nat.prime_def_lt at np,
  push_neg at np,
  rcases np (two_le_of_mod_4_eq_3 h) with ⟨m, mltn, mdvdn, mne1⟩,
  have mge : 2 ≤ m,
    apply two_le _ mne1,
    intro mz,
    rw [mz, zero_dvd_iff] at mdvdn, linarith,

  have neq : m * (n / m) = n := nat.mul_div_cancel' mdvdn,
  have : m % 4 = 3 ∨ (n / m) % 4 = 3,
    apply mod_4_eq_3_or_mod_4_eq_3,
    rw [neq, h],

  cases this with h' h',
  { by_cases mp : m.prime,
    { use [m, mp, mdvdn, h'],},
    { rcases ih m mltn h' mp with ⟨k, kp, kdvdm, keq⟩,
      use [k, kp, dvd_trans kdvdm mdvdn, keq],}},
  { have : (n / m) ∣ n ∧ (n / m) < n,
      from aux mdvdn mge mltn,
    by_cases nmp : (n / m).prime,
    { use [(n / m), nmp, this.1, h'],},
    { rcases ih (n /m) this.2 h' nmp with ⟨k, kp, kdvdm, keq⟩,
      use [k, kp, dvd_trans kdvdm this.1, keq],}}
end

example (m n : ℕ) (s : finset ℕ) (h : m ∈ erase s n) : m ≠ n ∧ m ∈ s :=
by rwa mem_erase at h

example (m n : ℕ) (s : finset ℕ) (h : m ∈ erase s n) : m ≠ n ∧ m ∈ s :=
by { simp at h, assumption }

theorem primes_mod_4_eq_3_infinite : ∀ n, ∃ p > n, nat.prime p ∧ p % 4 = 3 :=
begin
  by_contra' h,
  cases h with n hn,
  have : ∃ s : finset ℕ, ∀ p : ℕ, p.prime ∧ p % 4 = 3 ↔ p ∈ s,
    apply ex_finset_of_bounded,
    use n,
    contrapose! hn,
    rcases hn with ⟨p, ⟨pp, p4⟩, pltn⟩,
    use [p, pltn, pp, p4],

  cases this with s hs,
  have h₀ : 2 ≤ 4 * (∏ i in erase s 3, i) + 3,
    apply two_le; norm_num,

  have h₁ : (4 * (∏ i in erase s 3, i) + 3) % 4 = 3,
    rw [add_comm], norm_num,

  rcases exists_prime_factor_mod_4_eq_3 h₁ with ⟨p, pp, pdvd, p4eq⟩,

  have ps : p ∈ s,
    rw ←hs, use [pp, p4eq],

  have pne3 : p ≠ 3,
    intro pe3,
    rw [pe3, ←nat.dvd_add_iff_left (dvd_refl 3)] at pdvd,
    rw nat.prime_three.dvd_mul at pdvd,
    cases pdvd, norm_num at pdvd,
    have : 3 ∈ erase s 3,
      apply mem_of_dvd_prod_primes,
      from nat.prime_three,
      intros n ns,
      simp at ns,
      from ((hs n).2 ns.2).1,
      from pdvd,
    simpa,

  have : p ∣ 4 * (∏ i in erase s 3, i),
    rw nat.prime.dvd_mul pp,
    right, apply dvd_prod_of_mem,
    simp, split; assumption,

  have : p ∣ 3,
    convert nat.dvd_sub' pdvd this,
    simp,

  have : p = 3,
    apply nat.prime.eq_of_dvd_of_prime pp nat.prime_three this,

  contradiction,

end


/-
HOMEWORK #9

There is no need to import our previous definitions.
For this homework you will just use Lean's built-in
notations and definitions.
-/

/-
Prove the following. Note that you can read each of
the propositions to be proved as either a logical
statement or as simply a function definition. Use
what you already know about the latter to arrive
at a proof, and then understand the proof as one
that shows that the logical statement is true.
-/

theorem t1 {P Q : Prop} (p2q : P → Q) (p : P) : Q :=
  _

theorem t2 {P Q R : Prop} (p2q : P → Q) (q2r : Q → R): P → R :=
  λ (p : P),
    q2r (p2q p)

/-
Use "example" to state and prove the preceding two
theorems but using "cases" style notation rather
than C-style. Remember, "example" is a way to state
a proposition/type and give an example of a value.
Here's an example of the use of "example". Give
your answers following this example.
-/

-- example
example : ℕ := 5

-- Your two answers here

example : ∀ (P Q R : Prop), (P → Q) → (Q → R) → P → R :=
λ P Q R p2q q2r, λ p, _

/-
Now give English-language versions of your two proofs.
-/

/-
Prove the following using case analysis on one
of the arguments (i.e., use match...with...end
at a key point in your proof). Use "cases" style
notation.
-/

theorem t3 : ∀ (P : Prop), false → P
| P f := false.elim _

theorem t3' : ∀ (P : Prop), false → P
| P f := match f with /- no cases!-/ end

/-
Prove false → true by applying t3 to a proposition.
You have to figure out which one.
-/
theorem t4 : false → true := t3 true

/-
Define t5 to be the same as t3 but with P taken as
an implicit argument.
-/

theorem t5 : ∀ {P : Prop}, false → P   /- false elimination-/
| P f := false.elim f

/-
Define t6 to be a proof of false → true by
applying t5 to the right argument(s).
-/

theorem t6 : false → true := t5

/-
That is almost magic. In English, t3 proves
that false implies *any* proposition, so just
*apply* t3 to *true* in particular, but use t5
instead of t3.

What you see here is really important: Once
we've proved a general theorem (a ∀ proposition)
we can *apply the proof* to any *particular* case
to yield a proof for that specific case. This is
the elimination rule for ∀. It is also known as
universal instantiation (UI).
-/

/-
Next we see the idea that test cases are really
just equality propositions to be proved. Here,
for example, is a definition of the factorial
function.
-/

def fac : ℕ → ℕ
| 0 := 1
| (n' + 1) := (n' + 1) * fac n'

/-
Use "example" to write test cases for the
first ten natural number arguments to this
function.
-/

example : fac 0 = 1 := eq.refl 1
example : fac 1 = 1 := eq.refl _  -- Inferred
example : fac 2 = 2 := rfl        -- Shorthand
#check @rfl           -- infers type and value

-- The rest of your answers here

/-
Insight: A test case is an equality proposition.
It is proved by "running" the program under test
to reduce the application of the function to input
arguments to produce an output that is then asserted
to be equal to an expected output.

In many cases, all we have to do is to simplify
the expressions on each side of the eq to see if
they reduce to exactly the same value. If so, we
can *apply* eq.refl (a universal generalization!)
to that value. Using rfl we can avoid even having
to type that value in cases where Lean can infer
it.
-/

/-
The next problem requires thave you give a proof of
a bi-implication, a proposition whose connective is
↔. To prove a bi-implication requires that one prove
an implication in each direction.

Here you are asked to prove P ∧ Q ↔ Q ∧ P. What this
formula asserts is that ∧ is commutative. To construct
a proof of this proposition you will have to apply
iff.intro to two smaller proofs, one of P → Q and
and of Q → P.

Start by "assuming" that P and Q are arbitary but
specific propositions (∀ introduction), then apply
iff.intro to two "stubbed out" arguments (underscores).
We suggest that you put the underscores in parentheses
on different lines. Then recursively fill in each of
these stubs with the required types of proofs. Study
the context that Lean shows you in its Messages panel
to see what you have to work with at each point in
your proof constructions.
-/
theorem t7 : ∀ {P Q : Prop}, P ∧ Q ↔ Q ∧ P :=
λ P Q,
  iff.intro
    (λ pandq,
      and.intro _ _
    )
    _

-- P ∧ Q -- and.intro p q
-- P ↔ Q -- iff.intro p2q q2p
---- assume P then show Q (P → Q)
---- assume Q then show P (Q → P)

/-
In English, when asked to prove P ↔ Q, one says, "it
will suffice to show P → Q and then to show Q → P." One
then goes on to give a proof of each implication. It
then follows from iff.intro that a proof of P ↔ Q can
be constructed, proving the bi-implication.
-/

/-
The trick here is to do case analysis on porq
(use match ... with ... end) and to show that
a proof of R can be constructed *in either case*.
-/
theorem t8
          {P Q R : Prop}
          (p2r : P → R)
          (q2r : Q → R)
          (porq : P ∨ Q) :
          R := match porq with
                | or.inl p := p2r p
                | or.inr q := _
              end

theorem t8'
          {P Q R : Prop}
          (p2r : P → R)
          (q2r : Q → R)
          (porq : P ∨ Q) :
          R := or.elim porq p2r q2r

/-
Prove P ∨ Q → R. Well, it will suffice to
show P → R and then to show that Q → R (because
if we do that then we can use or.elim to get a
proof of R.)
-/

/-
We suggest that you use  "let ... in" to give
names to intermediate results that you then combine
in a final expression to finish the proof.
-/
theorem t9 : ∀ (P Q: Prop), (P → Q) → ¬ (P ∧ ¬ Q) :=
λ P Q,
  λ p2q,
    -- ¬ (P ∧ ¬ Q) === (P ∧ ¬ Q) → false
    λ ( pandnotq : (P ∧ ¬ Q)),
      (
        let p := pandnotq.left in
        let nq := pandnotq.right in
        let q := p2q p in
        nq q
      )

/-
To prove ¬P, assume P and show that this leads to
a contradiction, then from that derive a proof of
false, and use false.elim to finish the proof. This
is *proof by negation*.
-/

/-
The following is about proof by contradiction.
To prove P, assume ¬ P, and show that leads to
a contradiction. This shows ¬ (¬ P). Now, use
the *classical* principle of negation elimination
to deduce P.
-/

theorem neg_elim' : ∀ (P : Prop), ¬ ¬ P → P :=
λ P,
λ nnp,
_           -- STUCK!!

theorem neg_elim : ∀ (P : Prop), (P ∨ ¬ P) → (¬ ¬ P → P):=
λ P,
    λ pornotp,
        λ nnp,
          match pornotp /- P or ¬P -/ with
          | or.inl p := p
          | or.inr np := false.elim (_)
          end
        /-
            match excl_middle with
            | or.inl p := p
            | or.inr np := false.elim (nnp np)  -- false elimination
            end
        -/

-- nnp : (¬ P) → false
-- np : ¬ P
-- nnp np = false!

-- Let's use H to mean There is a sub-exponential time algorithm for Boolean sat.
-- ¬ H means that there's not one.
-- H ∨ ¬H

-- make Lean into a classical logic
-- axiom em : ∀ (P : Prop), P ∨ ¬ P
#check classical.em

theorem t10 : ∀ (P : Prop), P ∨ ¬ P :=
_

#check @or.inl
#check @or.inr

/-
DeMorgan's Laws
-/

theorem t11 : ∀ (P Q : Prop), ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
λ P Q,
    iff.intro
        (λ not_porq,
             match (classical.em P) with
             | or.inl p := false.elim (not_porq (or.inl p))
             -- ¬(P ∨ Q) === (P ∨ Q → false)
             | or.inr np := false.elim (not_porq _)
             end
        )
        (
          λ npandnq,
            λ porq,
              match porq with
              | or.inl p := _
              | or.inr q := _
              end
        )

theorem t12 : ∀ (P Q : Prop), ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
λ P Q,
  iff.intro
    (λ not_pandq,
      match (classical.em P) with
      | or.inl p := match (classical.em Q) with
                    | or.inl q := false.elim (not_pandq _)
                    | or.inr nq := _
                    end
      | or.inr np := _
      end
    )
    _

/-
For the following exercises, we assume that there is
a type called Person and a binary relation, Likes, on
pairs of people.
-/
axiom Person : Type
axiom Likes : Person → Person → Prop

/-
Prove the following
-/
theorem t13 :
  (∃ (p : Person), ∀ (q : Person), Likes q p) →
  (∀ (p : Person), ∃ (q : Person), Likes p q) :=
  λ h,
    λ p,
      match h with
      | exists.intro loved pf := exists.intro loved _
      end

/-
λ h,
    match h with
    | exists.intro p pf :=
        λ q,
            (exists.intro p (pf q))
    end
-/


import tactic
import data.int.parity
-- These are some helpful lemmas for coursework 3.
-- The main result in this file is the first theorem.

/-
A nonempty set of integers with a lower bound has a minimum.
-/
theorem bdd_below_Z_set_has_minimum {S : set ℤ} (hne : set.nonempty S) (hbdd : bdd_below S) :
∃ (m ∈ S), ∀(n ∈ S), m ≤ n :=
begin
  cases hne with m hm,
  change ∃ x, ∀ y ∈ S, x ≤ y  at hbdd,
  cases hbdd with lb hlb,
  have hml : lb ≤ m,
  {
    specialize hlb m,
    apply hlb,
    exact hm,
  },
  let T := finset.finite_to_set(finset.Icc lb m),
  let u := set.finite.inter_of_left T S,
  let V := set.finite.to_finset u,
  have minv : m ∈ V,
  {
    rw set.finite.mem_to_finset,
    split,
    {
      suffices : m ∈ (finset.Icc lb m),
      exact finset.mem_coe.mpr this,
      rw finset.mem_Icc,
      split, exact hml, refl,
    },
    exact hm,
  },
  have hnv : V.nonempty,
  {
    use m,
    exact minv,
  },
  let M := finset.min' V hnv,
  have hMm : M ≤ m,
  {
    have h2 := finset.min'_le V m minv,
    exact h2,
  },
  use M,
  split,
  {
    have h2 := finset.min'_mem V hnv,
    rw set.finite.mem_to_finset at h2,
    exact h2.right,
  },
  {
    intros n hn,
    by_cases n ≤ m,
    {
      apply finset.min'_le,
      rw set.finite.mem_to_finset,
      split,
      {
        rw finset.mem_coe,
        rw finset.mem_Icc,
        specialize hlb n,
        split,
        apply hlb, exact hn,
        exact h,
      },
      {
        exact hn,
      },
    },
    {
      rw not_le at h,
      linarith [h, hMm],
    }
  }
end

lemma le_abs_le {a b : ℚ} (h: a ≤  b):a ≤  |b| :=
begin
  have h2 := le_abs_self b,
  linarith,
end

namespace int
lemma odd_div {a b :ℤ} : a ∣ b → odd b → odd a :=
begin
  intros h1 h2,
  cases h1 with u hu,
  rw hu at h2,
  rw odd_mul at h2,
  exact h2.left,
end

end int

lemma sqrt_one_eqn (a : ℤ) : a^2 = 1 ↔ a = 1 ∨ a = -1 :=
begin
  split,
  {
    intro h,
    apply_fun (λ x, x - (1 : ℤ)) at h,
    rw (by ring : 1 - 1 = (0 : ℤ )) at h,
    rw (by ring: a^2 - 1 = (a + 1) * (a - 1)) at h,
    rw eq_comm at h,
    rw zero_eq_mul at h,
    cases h,
      right, linarith,
      left, linarith,
  },
  {
    intro h,
    cases h;
    rw h; norm_num,
  }
end

#lint