/-
This file defines the constructive real numbers ℛ,
defined by sequences of shrinking and dwindling rational segments
-/

import ..Intuitionism.segment

/--
We say that a sequence of segments is 'shrinking' if each segment is contained within its predecessor
-/
def shrinking (r : ℕ → 𝕊) := ∀ n, r (n + 1) ⊑ r n

/--
We say that a sequence of segments is 'dwindling' if we can make the segments arbitrarily small
-/
def dwindling (r : ℕ → 𝕊) := ∀ q : ℚ, q > 0 → ∃ n : ℕ, (r n).snd - (r n).fst < q

/--
The definition of real sequences ℛ, representing the real numbers
(Called 'real_seq' here to not interfere with the classical real numbers,
which are already defined in Lean using Cauchy sequences)
A real sequence is a sequence of rational segments that shrinks and dwindles
-/
def real_seq := {r : ℕ → 𝕊 // shrinking r ∧ dwindling r}

notation `ℛ` := real_seq

namespace real_seq

/--
Used to extract the underlying sequence of rational segments
-/
def seq (r : ℛ) : ℕ → 𝕊 := subtype.val r

-- We can turn a real segment into a sequence of rationals by only taking the first position
def fst (r : ℛ) : ℕ → ℚ := λ n, (r.seq n).fst

-- We can turn a real segment into a sequence of rationals by only taking the second position
def snd (r : ℛ) : ℕ → ℚ := λ n, (r.seq n).snd

lemma shrinking (r : ℛ) : shrinking r.val := (subtype.property r).elim_left

lemma dwindling (r : ℛ) : dwindling r.val := (subtype.property r).elim_right

theorem contained_of_le (r : ℛ) {n m : ℕ} (h₁ : n ≤ m) : r.seq m ⊑ r.seq n :=
begin
    induction h₁ with d hd,
    {-- need to prove: seq r n ⊑ seq r n
        refl,
    },
    {-- need to prove: seq r (nat.succ d) ⊑ seq r n
        transitivity (seq r d),
        {-- need to prove: seq r (nat.succ d) ⊑ seq r d
            exact r.shrinking d,
        },
        {-- need to prove: seq r d ⊑ seq r n
            exact h₁_ih,
        }
    }
end

def lt (x y : ℛ) : Prop := ∃ n : ℕ, x.seq n < y.seq n

infix `<` := lt

def le (x y : ℛ) : Prop := ∀ n : ℕ, x.seq n ≤ y.seq n

infix `≤` := le

def apart (x y : ℛ) : Prop := x < y ∨ y < x

infix `#` := apart

def eq (x y : ℛ) : Prop := ∀ n : ℕ, x.seq n ≈ y.seq n

infix `='`:50 := eq

def ne (x y : ℛ) : Prop := ¬ x =' y

infix `≠'`:50 := ne

@[trans] theorem lt_trans (x y z : ℛ) (h₁ : x < y) (h₂ : y < z) : x < z :=
begin
    cases h₁ with n hn,
    cases h₂ with m hm,
    use max m n,
    transitivity seq y (max m n),
    {-- need to prove: seq x (max m n) < seq y (max m n)
        have h₁ := contained_of_le x (le_max_right m n),
        have h₂ := contained_of_le y (le_max_right m n),
        apply lt_of_le_of_lt h₁.elim_right,
        apply lt_of_lt_of_le hn,
        exact h₂.elim_left,
    },
    {-- need to prove: seq y (max m n) < seq z (max m n)
        have h₁ := contained_of_le y (le_max_left m n),
        have h₂ := contained_of_le z (le_max_left m n),
        apply lt_of_le_of_lt h₁.elim_right,
        apply lt_of_lt_of_le hm,
        exact h₂.elim_left,
    }
end

lemma lt_or_lt_from_sub_lt_sub {a b c d : ℚ} (h : a - b < c - d) : a < c ∨ d < b :=
begin
    have hac := lt_trichotomy a c,
    cases hac with hl hr,
    {-- case: a < c
        left,
        exact hl,
    },
    {-- case: a = c ∨ c < a
        right,
        cases hr with heq hlt,
        {-- case: a = c
            rwa [heq, sub_eq_add_neg, sub_eq_add_neg, add_lt_add_iff_left, neg_lt_neg_iff] at h,
        },
        {-- case: c < a
            rw ← sub_lt_zero,
            transitivity (c-a),
            {-- need to prove: d - b < c - a
                rwa [lt_sub_iff_add_lt',
                    ← add_sub_assoc,
                    sub_lt_iff_lt_add',
                    ← lt_sub_iff_add_lt,
                    add_sub_assoc, add_comm,
                    ← sub_lt_iff_lt_add] at h,
            },
            {-- need to prove: c - a < 0
                rw ← sub_lt_zero at hlt,
                exact hlt,
            }
        }
    }
end

theorem lt_cotrans (x y z : ℛ) (h₁ : x < y) : x < z ∨ z < y :=
begin
    cases h₁ with n hn,
    rwa [segment.lt, ← sub_pos, ← gt_from_lt] at hn,
    cases z.dwindling (segment.fst (seq y n) - segment.snd (seq x n)) hn with m hm,
    cases lt_or_lt_from_sub_lt_sub hm with zlty xltz,
    {-- case: z.snd m < y.fst n
        right,
        use max m n,
        have h₁ := contained_of_le z (le_max_left m n),
        have h₂ := contained_of_le y (le_max_right m n),
        apply lt_of_le_of_lt h₁.elim_right,
        exact lt_of_lt_of_le zlty h₂.elim_left,
    },
    {-- case: x.snd n < z.fst m
        left,
        use max m n,
        have h₁ := contained_of_le z (le_max_left m n),
        have h₂ := contained_of_le x (le_max_right m n),
        apply lt_of_le_of_lt h₂.elim_right,
        exact lt_of_lt_of_le xltz h₁.elim_left,
    }
end

theorem apart_cotrans (x y z : ℛ) (h : x # y) : x # z ∨ z # y :=
begin
    cases h with h₁ h₂,
    {-- case: x < y
        cases lt_cotrans x y z h₁ with hz hz,
        {-- case: x < z
            left,
            left,
            exact hz,
        },
        {-- case: z < y
            right,
            left,
            exact hz,
        }
    },
    {-- case: case y < x
        cases lt_cotrans y x z h₂ with hz hz,
        {-- case: y < z
            right,
            right,
            exact hz,
        },
        {-- case: z < x
            left,
            right,
            exact hz,
        }
    }
end

theorem le_iff_not_lt (x y : ℛ) : x ≤ y ↔ ¬ y < x :=
begin
    split,
    {-- need to prove: x ≤ y → ¬ y < x
        intros h₁ h₂,
        cases h₂ with n hn,
        have hn₁ := h₁ n,
        rw segment.le_iff_not_lt at hn₁,
        exact hn₁ hn,
    },
    {-- need to prove: ¬ y < x → x ≤ y
        intros h n,
        rw segment.le_iff_not_lt,
        revert n,
        exact forall_not_of_not_exists h,
    }
end

theorem eq_of_le_of_le (x y : ℛ) : x ≤ y → y ≤ x → x =' y :=
begin
    intros hxy hyx n,
    split,
    {-- need to prove: seq x n ≤ seq y n
        exact hxy n,
    },
    {-- need to prove: seq y n ≤ seq x n
        exact hyx n,
    }
end

@[trans] theorem le_trans (x y z : ℛ) (h₁ : x ≤ y) (h₂ : y ≤ z) : x ≤ z :=
begin
    rw le_iff_not_lt at *,
    intro zltx,
    cases lt_cotrans z x y zltx with hz hz,
    {-- case: z < y
        exact h₂ hz,
    },
    {-- case: y < x
        exact h₁ hz,
    }
end

@[refl] theorem le_refl (x : ℛ) : x ≤ x :=
begin
    intro n,
    refl,
end

theorem eq_iff_not_apart (x y : ℛ) : x =' y ↔ ¬x # y :=
begin
    split,
    {-- need to prove: x = y → ¬ x # y
        intros h₁ h₂,
        cases h₂ with xlty yltx,
        {-- case: x < y
            cases xlty with n hn,
            have hn₁ := h₁ n,
            rw segment.lt_iff_not_le at hn,
            exact hn hn₁.elim_right,
        },
        {-- case: y < x
            cases yltx with n hn,
            have hn₁ := h₁ n,
            rw segment.lt_iff_not_le at hn,
            exact hn hn₁.elim_left,
        }
    },
    {-- need to prove: ¬ x # y → x = y
        intros h n,
        rw apart at h,
        rw segment.touches,
        rw not_or_distrib at h,
        split,
        {-- need to prove: seq x n ≤ seq y n
            rw segment.le_iff_not_lt,
            have h₁ := forall_not_of_not_exists h.elim_right n,
            exact h₁,
        },
        {-- need to prove: seq y n ≤ seq x n
            rw segment.le_iff_not_lt,
            have h₂ := forall_not_of_not_exists h.elim_left n,
            exact h₂,
        }
    }
end

@[trans] theorem eq_trans (x y z: ℛ) (h₁ : x =' y) (h₂ : y =' z) : x =' z :=
begin
    rw eq_iff_not_apart,
    intro h₃,
    cases apart_cotrans x z y h₃ with xay yaz,
    {-- case: x # y
        rw eq_iff_not_apart at h₁,
        exact h₁ xay,
    },
    {-- case: y # z
        rw eq_iff_not_apart at h₂,
        exact h₂ yaz,
    }
end

@[symm] theorem eq_symm (x y : ℛ) : x =' y ↔ y =' x :=
begin
    repeat {rw eq},
    simp [segment.touches_symm],
end

@[refl] theorem eq_refl (x : ℛ) : x =' x :=
begin
    intro n,
    refl,
end

theorem le_stable (x y : ℛ) : ¬¬x ≤ y → x ≤ y :=
begin
    rw le_iff_not_lt,
    exact not_of_not_not_not,
end

theorem eq_stable (x y : ℛ) : ¬¬x =' y → x =' y :=
begin
    rw eq_iff_not_apart,
    exact not_of_not_not_not,
end

/--
The inclusion from ℚ into ℛ
-/
def inclusion_const (q : ℚ) : ℛ :=
    subtype.mk (λ _ , segment.inclusion q)
    begin
        split,
        {-- need to prove: the defined real_seq is shrinking
            intro n,
            refl,
        },
        {-- need to prove: the defined real_seq is dwindling
            intros e he,
            use 0,
            rw gt_iff_lt at he,
            apply lt_of_le_of_lt _ he,
            apply le_of_eq,
            rw sub_eq_zero,
            refl,
        }
    end

@[instance] def has_zero : has_zero ℛ := { zero := inclusion_const 0 }

lemma zero : 0 = inclusion_const 0 := rfl

/--
The definition of + on real sequences
-/
def add (x y : ℛ) : ℛ := subtype.mk (λ n, segment.add (x.seq n) (y.seq n))
    begin
        have hx := subtype.property x,
        have hy := subtype.property y,
        split,
        {-- need to prove: shrinking
            intro n,
            split,
            {
                apply add_le_add,
                    exact (hx.elim_left n).elim_left,
                    exact (hy.elim_left n).elim_left,
            },
            {
                apply add_le_add,
                    exact (hx.elim_left n).elim_right,
                    exact (hy.elim_left n).elim_right,
            }
        },
        {-- need to prove: dwindling
            intros q hq,
            have hq2 : q / 2 > 0, by
            {
                rw gt_iff_lt,
                apply div_pos,
                exact gt_iff_lt.elim_left hq,
                exact zero_lt_two,
            },
            cases hx.elim_right (q / 2) hq2 with xn hxn,
            cases hy.elim_right (q / 2) hq2 with yn hyn,
            use max xn yn,
            simp [add_sub_comm, segment.add, segment.fst, segment.snd],
            have hqa : (q / 2) + (q / 2) = q, by
            {
                rw ← mul_two,
                rw div_mul_cancel,
                exact two_ne_zero,
            },
            rw ← hqa,
            apply add_lt_add,
            {-- need to prove: snd (x.seq (max xn yn)) - fst (x.seq (max xn yn)) < q / 2
                have hm : segment.snd (seq x (max xn yn)) - segment.fst (seq x (max xn yn)) ≤ (segment.snd (x.val xn) - segment.fst (x.val xn)), by
                {-- need to prove: snd - fst of x.seq (max xn yn) < snd - fst of x.seq xn
                    apply segment.contained_bounds_le,
                    apply contained_of_le _ (le_max_left _ _),
                },
                exact lt_of_le_of_lt hm hxn,
            },
            {
                have hm : segment.snd (seq y (max xn yn)) - segment.fst (seq y (max xn yn)) ≤ (segment.snd (y.val yn) - segment.fst (y.val yn)), by
                {-- need to prove: snd - fst of x.seq (max xn yn) < snd - fst of x.seq xn
                    apply segment.contained_bounds_le,
                    exact contained_of_le _ (le_max_right _ _),
                },
                exact lt_of_le_of_lt hm hyn,
            }
        }
    end

theorem add_assoc {x y z : ℛ} : add (add x y) z =' add x (add y z) :=
begin
    intro n,
    split,
    repeat {simp [seq, add, segment.add_assoc]},
end

theorem add_comm {x y : ℛ} : add x y =' add y x :=
begin
    intro n,
    split,
    repeat {simp [seq, add, segment.add_comm]},
end

theorem add_zero {x : ℛ} : add x 0 =' x :=
begin
    intro n,
    simp [zero, add, inclusion_const, seq, segment.add,
        segment.fst, segment.snd, segment.inclusion, segment.touches],
end

theorem zero_add {x : ℛ} : add 0 x =' x :=
begin
    transitivity (add x 0),
    exact add_comm,
    exact add_zero,
end

theorem eq_implies_add_eq_add {x y z : ℛ} : y =' z → add x y =' add x z :=
begin
    intros h n,
    have hn := h n,
    split,
    {-- need to prove: seq (add x y) n≤seq (add x z) n
        simp [seq, add, segment.le, segment.fst, segment.snd, segment.add],
        apply add_le_add,
        exact subtype.property (x.val n),
        exact hn.elim_left,
    },
    {
        simp [seq, add, segment.le, segment.fst, segment.snd, segment.add],
        apply add_le_add,
        exact subtype.property (x.val n),
        exact hn.elim_right,
    }
end

def neg (x : ℛ) : ℛ := subtype.mk (λ n, (x.val n).neg)
    begin
    	split,
        {-- shrinking
            intro n,
            split,
            { -- use: segment.snd (x.val (n + 1)) ≤ segment.snd (x.val n)
                simp [segment.fst, segment.fst, segment.neg, segment.neg, neg_le_neg_iff],
                exact ((subtype.property x).elim_left n).elim_right,
            },
            {--  use: segment.fst (x.val n) ≤ segment.fst (x.val (n + 1))
                simp [segment.snd, segment.snd, segment.neg, segment.neg, neg_le_neg_iff],
                exact ((subtype.property x).elim_left n).elim_left,
            }
        },
        {-- dwindling
            intros q hq,
            have hx := (subtype.property x).elim_right q hq,
            cases hx with xn hxn,
            use xn,
            have h :
                segment.snd ((λ (n : ℕ), segment.neg (x.val n)) xn) - segment.fst ((λ (n : ℕ), segment.neg (x.val n)) xn)
                    =
                segment.snd (x.val xn) - segment.fst (x.val xn),
            by simp [segment.snd, segment.fst, segment.neg],
            rw h,
            exact hxn,
        }
    end

def sub (x y : ℛ) : ℛ := add x (neg y)

theorem sub_self_eq_zero (x : ℛ) : sub x x =' 0 :=
begin
    rwa [zero, inclusion_const],
    intro n,
    simp [sub, add, neg, seq, segment.add, segment.neg, segment.fst, segment.snd, segment.touches, segment.inclusion],
    split,
    {-- sub x x ≤ 0
        simp [segment.le, segment.snd, segment.fst],
        exact (subtype.property (x.seq n)), --use: (x.seq n) is a segment
    },
    {-- 0 ≤ sub x x
        simp [segment.le, segment.snd, segment.fst],
        rwa [← sub_eq_add_neg, le_sub, sub_zero],
        exact (subtype.property (x.seq n)), --use: (x.seq n) is a segment
    }
end

theorem forall_exists_additive_inverse : ∀ x : ℛ, ∃ y : ℛ, add x y =' 0 :=
begin
    intro x,
    use neg x,
    rw ← sub,
    exact sub_self_eq_zero x,
end

-- In traditional notation: (x + y) - y = x
theorem sub_add (x y : ℛ) : sub (add x y) y =' x :=
begin
    transitivity add x (add y (neg y)),
    exact add_assoc,
    transitivity add x 0,
    {-- need to prove: add x (sub y y) =' add x 0
        exact eq_implies_add_eq_add (sub_self_eq_zero y),
    },
    exact add_zero,
end

theorem sub_add_comm {x y z : ℛ} : sub (add x y) z =' add x (sub y z) := add_assoc

end real_seq


-- Pruebas de la relación entre length y map
-- =========================================

import data.nat.basic
open nat
open list

variables {α : Type*} {β : Type*}
variable  (x : α)
variables (xs : list α)

-- ----------------------------------------------------
-- Nota. Se usarán la función longitud y sus
-- propiedades estudiadas anteriormente.
-- ----------------------------------------------------

def longitud : list α → nat
| []        := 0
| (x :: xs) := longitud xs + 1

@[simp]
lemma longitud_nil :
  longitud ([] : list α) = 0 :=
rfl

@[simp]
lemma longitud_cons :
  longitud (x :: xs) = longitud xs + 1 :=
rfl

-- ----------------------------------------------------
-- Ejercicio 3. Definir la función
--    aplica : (α → β) → list α → 'b list
-- tal que (aplica f xs) es la lista obtenida
-- aplicando la función f a los elementos de xs. Por
-- ejemplo,
--    aplica (λx, 2*x) [3,2,5] = [6,4,10]
--    aplica ((*) 2)   [3,2,5] = [6,4,10]
--    aplica ((+) 2)   [3,2,5] = [5,4,7]
-- ----------------------------------------------------

def aplica : (α → β) → list α → list β
| f []        := []
| f (x :: xs) := (f x) :: aplica f xs

-- #eval aplica (λx, 2*x) [3,2,5]
-- #eval aplica ((*) 2) [3,2,5]
-- #eval aplica ((+) 2) [3,2,5]

-- ----------------------------------------------------
-- Ejercicio 4. Demostrar los siguientes lemas
-- + aplica_nil :
--      aplica f [] = []
-- + aplica_cons :
--      aplica f (x :: xs) = (f x) :: aplica f xs
-- ----------------------------------------------------

@[simp]
lemma aplica_nil
  (f : α → β)
  : aplica f [] = [] :=
rfl

@[simp]
lemma aplica_cons
  (f : α → β)
  : aplica f (x :: xs) = (f x) :: aplica f xs :=
rfl

-- ----------------------------------------------------
-- Ejercicio 1. (p. 48) Demostrar que
--    longitud (aplica f xs) = longitud xs
-- ----------------------------------------------------

-- 1ª demostración
example
  (f : α → β)
  : longitud (aplica f xs) = longitud xs :=
begin
  induction xs with a as HI,
  { rw aplica_nil,
    rw longitud_nil,
    rw longitud_nil, },
  { rw aplica_cons,
    rw longitud_cons,
    rw HI,
    rw longitud_cons, },
end

-- 2ª demostración
example
  (f : α → β)
  : longitud (aplica f xs) = longitud xs :=
begin
  induction xs with a as HI,
  { calc longitud (aplica f [])
         = longitud []                   : by rw aplica_nil
     ... = 0                             : by rw longitud_nil
     ... = longitud []                   : by rw longitud_nil, },
  { calc longitud (aplica f (a :: as))
         = longitud (f a :: aplica f as) : by rw aplica_cons
     ... = longitud (aplica f as) + 1    : by rw longitud_cons
     ... = longitud as + 1               : by rw HI
     ... = longitud (a :: as)            : by rw longitud_cons, },
end

-- 3ª demostración
example
  (f : α → β)
  : longitud (aplica f xs) = longitud xs :=
begin
  induction xs with x xs HI,
  { simp, },
  { simp [HI], },
end

-- 4ª demostración
example
  (f : α → β)
  : longitud (aplica f xs) = longitud xs :=
by induction xs ; simp [*]

-- 5ª demostración
lemma longitud_aplica
  (f : α → β)
  : ∀ xs, longitud (aplica f xs) = longitud xs
| []        := by simp
| (a :: as) := by simp [longitud_aplica as]

-- Comentarios sobre las función map:
-- + Es equivalente a la función aplica.
-- + Para usarla hay que importar la librería
--   data.list.basic y abrir el espacio de nombre
--   list escribiendo al principio del fichero
--      import data.list.basic
--      open list
-- + Se puede evaluar. Por ejemplo,
--      #eval map (λx, 2*x) [3,2,5]
--      #eval map ((*) 2) [3,2,5]
--      #eval map ((+) 2) [3,2,5]
-- + Se puede demostrar. Por ejemplo,
example
  (f : α → β)
  : length (map f xs) = length xs :=
by induction xs ; simp [*]

example
  (f : α → β)
  : length (map f xs) = length xs :=
-- by suggest
length_map f xs

example
  (f : α → β)
  : length (map f xs) = length xs :=
by simp


/-
Copyright (c) 2020 Patrick Stevens. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Stevens, Yury Kudryashov
-/
import algebra.big_operators.associated
import data.nat.choose.sum
import data.nat.choose.dvd
import data.nat.parity
import data.nat.prime

/-!
# Primorial

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

This file defines the primorial function (the product of primes less than or equal to some bound),
and proves that `primorial n ≤ 4 ^ n`.

## Notations

We use the local notation `n#` for the primorial of `n`: that is, the product of the primes less
than or equal to `n`.
-/

open finset
open nat
open_locale big_operators nat

/-- The primorial `n#` of `n` is the product of the primes less than or equal to `n`.
-/
def primorial (n : ℕ) : ℕ := ∏ p in (filter nat.prime (range (n + 1))), p
local notation x`#` := primorial x

lemma primorial_pos (n : ℕ) : 0 < n# :=
prod_pos $ λ p hp, (mem_filter.1 hp).2.pos

lemma primorial_succ {n : ℕ} (hn1 : n ≠ 1) (hn : odd n) : (n + 1)# = n# :=
begin
  refine prod_congr _ (λ _ _, rfl),
  rw [range_succ, filter_insert, if_neg (λ h, odd_iff_not_even.mp hn _)],
  exact (h.even_sub_one $ mt succ.inj hn1)
end

lemma primorial_add (m n : ℕ) :
  (m + n)# = m# * ∏ p in filter nat.prime (Ico (m + 1) (m + n + 1)), p :=
begin
  rw [primorial, primorial, ← Ico_zero_eq_range, ← prod_union, ← filter_union,
    Ico_union_Ico_eq_Ico],
  exacts [zero_le _, add_le_add_right (nat.le_add_right _ _) _,
    disjoint_filter_filter $ Ico_disjoint_Ico_consecutive _ _ _]
end

lemma primorial_add_dvd {m n : ℕ} (h : n ≤ m) : (m + n)# ∣ m# * choose (m + n) m :=
calc (m + n)# = m# * ∏ p in filter nat.prime (Ico (m + 1) (m + n + 1)), p :
  primorial_add _ _
... ∣ m# * choose (m + n) m :
  mul_dvd_mul_left _ $ prod_primes_dvd _ (λ k hk, (mem_filter.1 hk).2.prime) $ λ p hp,
    begin
      rw [mem_filter, mem_Ico] at hp,
      exact hp.2.dvd_choose_add hp.1.1 (h.trans_lt (m.lt_succ_self.trans_le hp.1.1))
        (nat.lt_succ_iff.1 hp.1.2)
    end

lemma primorial_add_le {m n : ℕ} (h : n ≤ m) : (m + n)# ≤ m# * choose (m + n) m :=
le_of_dvd (mul_pos (primorial_pos _) (choose_pos $ nat.le_add_right _ _)) (primorial_add_dvd h)

lemma primorial_le_4_pow (n : ℕ) : n# ≤ 4 ^ n :=
begin
  induction n using nat.strong_induction_on with n ihn,
  cases n, { refl },
  rcases n.even_or_odd with (⟨m, rfl⟩ | ho),
  { rcases m.eq_zero_or_pos with rfl | hm, { dec_trivial },
    calc (m + m + 1)# = (m + 1 + m)# : by rw [add_right_comm]
    ... ≤ (m + 1)# * choose (m + 1 + m) (m + 1) : primorial_add_le m.le_succ
    ... = (m + 1)# * choose (2 * m + 1) m : by rw [choose_symm_add, two_mul, add_right_comm]
    ... ≤ 4 ^ (m + 1) * 4 ^ m :
      mul_le_mul' (ihn _ $ succ_lt_succ $ (lt_add_iff_pos_left _).2 hm) (choose_middle_le_pow _)
    ... ≤ 4 ^ (m + m + 1) : by rw [← pow_add, add_right_comm] },
  { rcases decidable.eq_or_ne n 1 with rfl | hn,
    { dec_trivial },
    { calc (n + 1)# = n# : primorial_succ hn ho
      ... ≤ 4 ^ n : ihn n n.lt_succ_self
      ... ≤ 4 ^ (n + 1) : pow_le_pow_of_le_right  four_pos n.le_succ } }
end


/- LoVe Exercise 5: Inductive Predicates -/

import .love05_inductive_predicates_demo

namespace LoVe

/- Question 1: Even and Odd -/

/- The `even` predicate is true for even numbers and false for odd numbers. -/

#check even

/- 1.1. Prove that 0, 2, 4, and 6 are even. -/

-- enter your answer here

/- We define `odd` as the negation of `even`: -/

def odd (n : ℕ) : Prop :=
  ¬ even n

/- 1.2. Prove that 1 is odd and register this fact as a `simp` rule.

Hint: `cases` is useful to reason about hypotheses of the form `even …`. -/

@[simp] lemma odd_1 :
  odd 1 :=
sorry

/- 1.3. Prove that 3, 5, and 7 are odd. -/

-- enter your answer here

/- 1.4. Complete the following proof by structural induction.

Hint: You can rely implicitly on computation for the induction step. -/

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
:= sorry

/- 1.5. Complete the following proof by rule induction.

Hint: You can use the `cases` tactic (or `match … with`) to destruct an
existential quantifier and extract the witness. -/

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m
| _ even.zero            := exists.intro 0 (by simp)
| _ (even.add_two n hen) :=
sorry

/- 1.6. Using `even_two_times` and `even_imp_exists_two_times`, prove the
following equivalence. -/

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
sorry

/- Question 2: Binary Trees -/

/- 2.1. Prove the converse of `is_full_mirror`. You may exploit already proved
lemmas (e.g., `is_full_mirror`, `mirror_mirror`). -/

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
sorry

/- 2.2. Define a function that counts the number of constructors (`empty` or
`node`) in a tree. -/

def count {α : Type} : btree α → ℕ
:= sorry

end LoVe


import .satisfiability
import .rules_of_reasoning
..
/-
CS2102 Exam #2: Propositional Logic
-/

-- You submitted an exam: [3 points free!]

/- [20 points]

    2 for header
    9 for reaching until list of interpretations (First 2 lines of is_satisfaible)
    4.5 for the helper function (fold_or) and
    4.5 for calling it from is_satisfiable

#1. Define a predicate function, is_satisfiable: pExp → bool
that returns true iff the given proposition is satisfiable.
Hint: Model your answer on our definition of is_valid. You
may need to define one or more helper functions. You should
test your solution, but we will only grade your definition.
Please write test cases in a separate file. Do not submit
that file.
-/
--returns true if at least one interpretation results in
--the expression being true
def foldr_or : list bool → bool
| [] := ff
| (h::t) := bor h (foldr_or t)

-- Answer here
def is_satisfiable (e : pExp) : bool :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    foldr_or rs

/-

[8 points]
1 point each

#2. Clearly the propositions in rules_of_reasoning.lean
that are valid are also satisfiable. But what about the following?
Apply your satisfiability predicate function to decide whether
or not each of the following formulae is satisfiable or not.
Write an "#eval is_satisfiable e" command for each expression.

A. The "fallacies" in that file (the ones that aren't valid).

B. The proposition, pFalse.

C. The following propositions:

1. P ∧ ¬ P
2. P ∨ ¬ P
3. = (¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)

Note that for 3. you will have to define four new variables.
Call them x1, x2, x3, and x4.

x1...x4 are needed for last one but is not graded

-/
def x1 : pExp := pExp.pVar (var.mk 0)
def x2 : pExp := pExp.pVar (var.mk 1)
def x3 : pExp := pExp.pVar (var.mk 2)
def x4 : pExp := pExp.pVar (var.mk 3)

-- Answers here
--All satisfiable but not valid
#eval is_satisfiable true_imp
#eval is_satisfiable affirm_consequence
#eval is_satisfiable affirm_disjunct
#eval is_satisfiable deny_antecedent

--False is unsatisfiable
#eval is_satisfiable pExp.pFalse

#eval is_satisfiable (P ∧ ¬ P) --unsatisfiable
#eval is_satisfiable (P ∨ ¬ P) --satisfiable (valid)
#eval is_satisfiable ((¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4))
--also satisfiable (all true)

/- 20 POINTS

-- 8 points for generating all interpretations
-- 8 points for seeing that you need to find a satisfying one
-- 4 points for returning the value correctly as an option value

3. In the previous problems, you defined a satisfiability
predicate function that returns true or false depending on
whether a given formula is satisfiable or not. Often we will
want to know not only whether there exists a solution but an
actual example of a solution, if there is one. Define a function
called sat_solver : pExp → option (var → bool) that returns a
satisfying interpretation (as "some interpretation") if there
is one, or "none" otherwise. Hint: Model your solution on our
validity checker. First compute the list of interpretations for
a given expression, e, then reduce that list to a value of type
option (var → bool). Evaluate e under each interpretation in
the list until either (A) you find one, call it i, for which e
evaluates to true, or until you reach the empty list, empty
handed.
-/

-- Answer here
--helper function
def reduce_interpretations : pExp → list (var → bool) → option (var → bool)
| e list.nil := none
| e (h::t) := match (pEval e h) with
        | tt := some h
        | ff := reduce_interpretations e t
        end

def sat_solver (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    reduce_interpretations e is

-- Example (OPTIONAL)
#reduce sat_solver (P ∧ Q >> (¬ P))

-- for getting the truth value you can also do (OPTIONAL)

def option_to_interp'' : option (var → bool) → (var → bool)
| none := λ (v : var), ff
| (some i) := i

#reduce option_to_interp'' (sat_solver (P ∧ Q >> (¬ P)))

#eval pEval (P ∧ Q >> (¬ P)) (option_to_interp'' (sat_solver (P ∧ Q >> (¬ P))))

/- [10 points] All or nothing/ Just make sure it works

4. Define a predicate function, is_unsatisfiable : pExp → bool,
that takes a propositional logic formula, e, and returns true
iff e is unsatisfiable. Hint: Easy. Build on what you have.
-/

-- Answer here

-- 3 different ways:

--1
---------------------------------------------------------------------
def is_unsatisfiable (e : pExp) : bool := ¬ (is_satisfiable e)
---------------------------------------------------------------------
--2
---------------------------------------------------------------------
def F_to_T : list bool → list bool
| [] := []
| (h::t) := ((¬h) :: F_to_T t)

def is_unsatisfiable' (e : pExp) : bool :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    let ft := F_to_T rs in
    foldr_and ft
---------------------------------------------------------------------
--3
---------------------------------------------------------------------
def is_unsatisfiable'' : pExp → bool
| e := match (sat_solver e) with
        | none := tt
        | _ := ff
        end
---------------------------------------------------------------------

-- For proof checking check these propositions

#eval is_unsatisfiable ((P ∧ ¬ Q) ∨ ((Q ∧ ¬ P)))        --ff
#eval is_unsatisfiable ((P ∧ ¬ Q))                      --ff
#eval is_unsatisfiable (P ∧ ¬ P)                        --tt
#eval is_unsatisfiable pExp.pFalse                      --tt
#eval is_unsatisfiable pExp.pTrue                       --ff

/- [15 points]

4 => for finding list of interps
8 points => Find which interps work as a counter example (match...with or if..else or simply called sat_solver with ¬e)
3 => returning as an option

5. Given a propositional logic expression, e, and an incorrect
claim that it's valid, we often want to produce a counter-example
to that claim. Such a counter example is an interpretation under
which the expression is not true. Equivalently, a counterexample
is an interpretation under which the *negation* of the expression
*is* true.

Define a function, counterexample : pExp → option (var → bool),
that takes any expression e and returns either a counterexample
(as "some interpretation") or "none" if there isn't one. Hint:
given e, try to find a model for the expression, ¬ e.

-/

-- Answer here
-- 2 different ways
--1
---------------------------------------------------------------------
def mk_counter_example : pExp → list (var → bool) → option (var → bool)
| e list.nil := none
| e (h::t) := match (pEval e h) with
        | ff := some h
        | tt := mk_counter_example e t
        end

def counter_example (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    mk_counter_example e is
---------------------------------------------------------------------
--2
---------------------------------------------------------------------
def counter_example' (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    sat_solver (¬ e)
---------------------------------------------------------------------

-- OPTIONAL for checking

def option_to_interp : option (var → bool) → (var → bool)
| none := λ (v : var), ff
| (some i) := i

#reduce pEval deny_antecedent (option_to_interp (counter_example' deny_antecedent))

/- ONE POINT EACH (24 points)

6. Give an English language example for every valid rule of
reasoning in rules_of_reasoning.lean, and also give an English
language counter-example for each fallacy.

For example, for the rule, P >> Q >> P ∧ Q, you could say,
"If the ball is red then if (in addition to that) the box
is blue, then (in this context) the ball is red AND the box
is blue." It will be easiest is you re-use the same words
for P, Q, and R, in all of your examples. E.g., P means "the
ball is red", Q means "the box is blue", etc.

Copy the contents of rules_of_reasoning.lean into this comment
block and under each rule give your English language sentence.
-/

--english examples etc.

/-
def true_intro : pExp := pTrue

True is true.

def false_elim := pFalse >> P

False implies anything.
If false is true then anything is true.

def true_imp := pTrue >> P

Fallacy.
True implies anything.
If true is true then anything is true.
If 1=1 then 1=0.

def and_intro := P >> Q >> P ∧ Q

If it's raining then if the streets
are wet then it's raining and the streets are wet.

def and_elim_left := P ∧ Q >> P

If it's raining and the streets are wet then it's raining.

def and_elim_right := P ∧ Q >> Q

If it's raining and the streets are wet then the streets are wet.

def or_intro_left := P >> P ∨ Q

If it's raining then it's raining or the the streets are wet.

def or_intro_right := Q >> P ∨ Q

If the streets are wet then it's raining or the the streets are wet.

------------------------------------------------------------------------------ (First 8)

def or_elim := P ∨ Q >> (P >> R) >> (Q >> R) >> R

If it's raining or the sprinkler is running, then if whenever its
raining the streets are wet, then if whenever the sprinkler is running
the streets are wet, then the streets are wet.

def iff_intro := (P >> Q) >> (Q >> P) >> (P ↔ Q)

If it's the case that if it's spring the flowers are blooming, and
then it's also the case that if the flowers are blooming then it's
springtime, then the flowers are blooming if and only if it's spring.

def iff_intro' := (P >> Q) ∧ (Q >> P) >> (P ↔ Q)

If it's the case that if it's spring the flowers are blooming, and
it's also the case that if the flowers are blooming then it's
springtime, then the flowers are blooming if and only if it's spring.

def iff_elim_left := (P ↔ Q) >> (P >> Q)

If the flowers are blooming if and only if it's spring, then if the
flowers are blooming then it's spring.

def iff_elim_right := (P ↔ Q) >> (Q >> P)

If the flowers are blooming if and only if it's spring, then if it's
spring then the flowers are blooming.

def arrow_elim := (P >> Q) >> (P >> Q)

If whenever it's raining the streets are wet, then if it's raining,
the streets are wet.

def resolution := (P ∨ Q) >> (¬ Q ∨ R) >> (P ∨ R)

If the pizza is hot (P) or the soda is cold (Q), then if the soda is
warm (¬ Q) or the cheese sticks are tasty (R), then the pizza is hot
or the cheese sticks are tasty.

def unit_resolution := (P ∨ Q) >> ((¬ Q) >> P)

If the pizza is hot or the soda is cold, then if the soda is not cold,
the pizza must be hot.

-------------------------------------------------------------------------------------- (Middle 8)

def syllogism := (P >> Q) >> (Q >> R) >> (P >> R)

If being sick makes you sad, and being sad makes you cry, then
being sick makes you cry.

def modus_tollens := (P >> Q) >> (¬ Q >> ¬ P)

If being sick makes you cry, then if you're not crying you must
not be sick.

def neg_elim := (¬ ¬ P) >> P

If a natural number, n, is not not even, then n is even.

If you're not not happy, then you're happy.
(The connotation of not not happy in English is not really exactly
the same as the connotation of happy. So here the English and the
logic don't quite align.)

def excluded_middle := P ∨ (¬ P)

A natural number, n, is even or n is not even.

def neg_intro := (P >> pFalse) >> (¬ P)

If the assumption that a = b let's you deduce something that's
impossible, then a=b can't be true, and must be false.

def affirm_consequence := (P >> Q) >> (Q >> P)

If it's raining then the streets are wet, then if the streets are wet
it must be raining. This is fallacy, as the streets could be wet even if
it's not raining (by other causes).

def affirm_disjunct := (P ∨ Q) >> (P >> ¬ Q)

Professor Foo is great or Prof. Bar is great, so if Prof. Foo is great
then Prof. Bar must not be. This is also a fallacy, as both can be great.

def deny_antecedent := (P >> Q) >> (¬ P >> ¬ Q)

If it's raining means the streets are wet, then if it's not raining the
streets must be dry. Also a fallacy, for the same reason as above. The
streets could be wet even it's not raining. Maybe someone spilled their
drink.
-------------------------------------------------------------------------------- (Last 8)

-/


import data.int.modeq

/-
Let p be a prime number and k a positive integer.
(a) Show that if x is an integer such that x^2 ≡ x mod p, then x ≡ 0 or 1 mod p.
(b) Show that if x is an integer such that x^2 ≡ x mod p^k, then x ≡ 0 or 1 mod p^k.
-/

lemma part_a (p : ℤ) (hp : prime p) (x : ℤ) : x^2 ≡ x [ZMOD p] → x ≡ 0 [ZMOD p] ∨ x ≡ 1 [ZMOD p] :=
begin
  sorry
end

lemma part_b (p : ℤ) (hp : prime p) (k : ℕ) (hk : k > 0) (x : ℤ) : x^2 ≡ x [ZMOD p^k] → x ≡ 0 [ZMOD p^k] ∨ x ≡ 1 [ZMOD p^k] :=
begin
  sorry
end


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

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 3 : not in (`∉`) and complement `Aᶜ`

The definition in Lean of `x ∉ A` is `¬ (x ∈ A)`. In other words,
`x ∉ A`, `¬ (x ∈ A)` and `(x ∈ A) → false` are all equal *by definition*
in Lean.

The complement of a subset `A` of `X` is the subset `Aᶜ`; it's the terms of
type `X` which aren't in `A`. The *definition* of `x ∈ Aᶜ` is `x ∉ A`.

For example, if you have a hypothesis `h : x ∈ Aᶜ` and your goal
is `false`, then `apply h` will work and will change the goal to `x ∈ A`.
Think a bit about why.

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : x ∉ A → (x ∈ A → false) :=
begin
  intro h,
  exact h,
end

example : x ∈ A → (x ∉ A → false) :=
begin
  intros h1 h2,
  exact h2 h1,
end

example : (A ⊆ B) → x ∉ B → x ∉ A :=
begin
  intros hAB hB hA,
  apply hB,
  exact hAB hA,
end

-- Lean couldn't work out what I meant when I wrote `x ∈ ∅` so I had
-- to give it a hint by telling it the type of `∅`.
example : x ∉ (∅ : set X):=
begin
  intro h,
  exact h,
end

example : x ∈ Aᶜ ↔ x ∉ A :=
begin
  refl,
end

example : (∀ x, x ∈ A) ↔ ¬ (∃ x, x ∈ Aᶜ) :=
begin
  -- `hint` tells you some tactics which do this in one line
  -- `simp`, `finish`, `tidy` all work and so do other things
  -- also `exact not_exists_not.symm` works
  -- And `library_search` works after
  -- `change (∀ x, x ∈ A) ↔ ¬ (∃ x, x ∉ A)`
  split,
  { rintros h1 ⟨x, hx⟩,
    exact hx (h1 x) },
  { intros h x,
    by_contra h2,
    apply h,
    exact ⟨x, h2⟩ },
end

example : (∃ x, x ∈ A) ↔ ¬ (∀ x, x ∈ Aᶜ) :=
begin
  finish,
end


variables p q r s : Prop

theorem and_commutative (p q : Prop) : p ∧ q → q ∧ p :=
assume hpq : p ∧ q,
have hp : p, from and.left hpq,
have hq : q, from and.right hpq,
show q ∧ p, from and.intro hq hp

theorem or_commutative (p q : Prop) : p ∨ q → q ∨ p :=
assume hpq : p ∨ q,
show q ∨ p,
from or.elim hpq
    (assume hp : p,
    show q ∨ p, from or.intro_right q hp)
    (assume hq: q,
    show q ∨ p, from or.intro_left p hq)

theorem and_associ (h : (p ∧ q) ∧ r) : p ∧ (q ∧ r) :=
have hpq : p ∧ q, from and.left h,
have hr : r, from and.right h,
have hp : p, from and.left hpq,
have hq : q, from and.right hpq,
have hqr : q ∧ r, from and.intro hq hr,
show p ∧ (q ∧ r), from and.intro hp hqr

theorem or_associ (h : (p ∨ q) ∨ r) : p ∨ (q ∨ r) :=
show p ∨ (q ∨ r),
from or.elim h
    (assume hpq : p ∨ q,
    show p ∨ (q ∨ r), from
    or.elim hpq
        (assume hp : p, or.inl hp)
        (assume hq : q, or.inr (or.inl hq))
    )
    (assume hr : r,
    show p ∨ (q ∨ r), from
    or.intro_right p (or.intro_right q hr))

theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
  apply and.intro,
  exact hp,
  apply and.intro,
  exact hq,
  exact hp
end

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
  apply iff.intro,
    intro h,
    apply or.elim (and.right h),
      intro hq,
      apply or.inl,
      apply and.intro,
        exact and.left h,
      exact hq,
    intro hr,
    apply or.inr,
    apply and.intro,
      exact and.left h,
    exact hr,
  intro h,
  apply or.elim h,
    intro hpq,
    apply and.intro,
      exact and.left hpq,
    apply or.inl,
    exact and.right hpq,
  intro hpr,
  apply and.intro,
    exact and.left hpr,
  apply or.inr,
  exact and.right hpr
end

theorem and_associ_tac (h : (p ∧ q) ∧ r) : p ∧ (q ∧ r) :=
begin
apply and.intro,
exact and.left (and.left h),
apply and.intro,
exact and.right (and.left h),
exact and.right h
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros,
  transitivity,
  symmetry,
  assumption,
  assumption
end


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intros h h2,
  apply h2,
  contradiction,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro h,
  by_cases h : P,
  assumption,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  intro h,
  by_cases h: P,
  assumption,
  contradiction,
  intros h h2,
  contradiction,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------
-- this might be useful: apply or.inl

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

end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro h,
  cases h,
  split,
  exact h_right,
  exact h_left,

end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intros h h2,
  cases h with c1 c2,
  contradiction,
  exact c2,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros h h2,
  cases h with c1 c2,
  contradiction,
  exact c2,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros h h2 h3,
  have hq : Q := h h3,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros h h2,
  by_cases h3 : Q,
  assumption,
  have homi : ¬P := h h3,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  intros h h2 h3,
  have hq : Q := h h3,
  contradiction,
  intros h h2,
  by_cases h3 : Q,
  assumption,
  have homi : ¬P := h h3,
  contradiction,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro h,
  have h2 : P∨¬P,
    right,
    intro h3,
    have h4 : P∨¬P,
      left,
      assumption,
    apply h h4,
    apply h h2,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros h h2,
  have hnew : (P → Q),
  intro h3,
  contradiction,
  have homi : P := h hnew,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros h h2,
  cases h2,
  cases h,
  contradiction,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intros h h2,
  cases h,
  cases h2,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro h,
  split,
  intro h_1,
  have h1 : P∨Q,
    left,
    assumption,
  contradiction,
  intro h_1,
  have h1 : P∨Q,
    right,
    assumption,
  contradiction,

end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros h1 h2,
  cases h1,
  cases h2,
  contradiction,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro h,
  by_cases h2 : P,
  by_cases h3 : Q, --duas bolas de fogo são melhores que uma!
  have h4 : P ∧ Q,
  split,
  assumption,
  assumption,
  contradiction,
  left,
  assumption,
  right,
  assumption,

end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros h1 h2,
  cases h2,
  cases h1,
  contradiction,
  contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  --eu n sei importar teorema help
    intro h,
    by_cases h2 : P,
    by_cases h3 : Q, --duas bolas de fogo são melhores que uma!
    have h4 : P ∧ Q,
    split,
    assumption,
    assumption,
    contradiction,
    left,
    assumption,
    right,
    assumption,
  --FIM DE: eu n sei importar teorema help
  intros h h2,
  cases h2,
  cases h,
  contradiction,
  contradiction,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  intro h,
  split,
  intro h1,
  have Q : P∨Q,
    left,
    assumption,
  contradiction,
  intro h1,
  have Q : P∨Q,
    right,
    assumption,
  contradiction,
  intros h h2,
  cases h,
  cases h2,
  contradiction,
  contradiction,

end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro h,
  cases h,
  cases h_right,
  left,
  split,
  assumption,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro h,
  cases h,
  cases h,
  split,
  assumption,
  left,
  assumption,
  cases h,
  split,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro h,
  cases h,
  split,
  left,
  assumption,
  left,
  assumption,
  cases h,
  split,
  right,
  assumption,
  right,
  assumption,

end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro h,
  cases h,
  cases h_left,
  cases h_right,
  left,
  assumption,
  left,
  assumption,
  cases h_right,
  left,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros h h2 h3,
  have hq : P∧Q,
    split,
    assumption,
    assumption,
  apply h,
  assumption,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros h h1,
  cases h1,
  apply h,
  assumption,
  assumption, --tendi n ;-;

end

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

theorem impl_refl :
  P → P  :=
begin
  intro h,
  assumption,
end

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

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro h,
  left,
  assumption,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro h,
  right,
  assumption,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro h,
  cases h,
  assumption,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro h,
  cases h,
  assumption,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro h,
  cases h,
  assumption,
  intro h,
  split,
  assumption,
  assumption,

end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro h,
  cases h,
  assumption,
  assumption,
  intro h,
  right,
  assumption,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intros h a h1,
  have h2 : (∃x, P x),
  existsi a,
  assumption,
  contradiction,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros h h2,
  cases h2 with a ha,
  have hq : ¬P a := h a,
  contradiction,

end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h,
  -- eu n sei importar teorema help
  have h2 : ¬(∃x, ¬P x) → (∀x, P x),
    intros h_ a,
    by_cases hcases : P a,
    assumption,
    have h_2 : (∃x, ¬P x),
      existsi a,
      assumption,
    contradiction,
  --FIM DE: eu n sei importar teorema help
  by_contradiction h3,
  have h4 : (∀x, P x) := h2 h3,
  contradiction,

end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro h,
  cases h with a ha,
  intro a2,
  have hq : P a := a2 a,
  contradiction,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  -- eu n sei importar teorema help
  intro h,
  -- eu n sei importar teorema help
  have h2 : ¬(∃x, ¬P x) → (∀x, P x),
    intros h_ a,
    by_cases hcases : P a,
    assumption,
    have h_2 : (∃x, ¬P x),
      existsi a,
      assumption,
    contradiction,
  --FIM DE: eu n sei importar teorema help
  by_contradiction h3,
  have h4 : (∀x, P x) := h2 h3,
  contradiction,
  --FIM DE: eu n sei importar teorema help
  -- eu n sei importar teorema help
  intro h,
  cases h with a ha,
  intro a2,
  have hq : P a := a2 a,
  contradiction,
  --FIM DE: eu n sei importar teorema help
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  intros h a h1,
  have h2 : (∃x, P x),
    existsi a,
    assumption,
  contradiction,
  intros h h1,
  cases h1 with a ha,
  have h2000 : ¬P a := h a,
  contradiction,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros h h2,
  cases h with a ha,
  have hq : ¬P a := h2 a,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros h h2,
  cases h2 with a ha,
  have hq: P a := h a,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros h a,
  by_cases h2 : P a,
  assumption,
  have h3 : (∃x, ¬P x),
    existsi a,
    assumption,
  contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro h,
  by_contradiction h2,
  have h3: ¬(∃x, P x) → (∀x, ¬P x),
    intros h4 a h5,
    have h6 : (∃x, P x),
      existsi a,
      assumption,
    contradiction,
  have h4 : (∀x, ¬P x) := h3 h2,
  contradiction,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  intros h h2,
  cases h2 with a ha,
  have hq: P a := h a,
  contradiction,
  intro h,
  intro a,
  by_contradiction h2,
  have h3 : (∃x, ¬P x),
    existsi a,
    assumption,
  contradiction,

end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  intros h h2,
  cases h with a ha,
  have hq: ¬P a := h2 a,
  contradiction,
  -- eu n sei importar teorema help
  intro h,
  by_contradiction h2,
  have h3: ¬(∃x, P x) → (∀x, ¬P x),
    intros h4 a h5,
    have h6 : (∃x, P x),
      existsi a,
      assumption,
    contradiction,
  have h4 : (∀x, ¬P x) := h3 h2,
  contradiction,
  --FIM DE: eu n sei importar teorema help

end

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro h,
  cases h with a ha,
  cases ha,
  split,
  existsi a,
  assumption,
  existsi a,
  assumption,

end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro h,
  cases h with a ha,
  cases ha,
  left,
  existsi a,
  assumption,
  right,
  existsi a,
  assumption,

end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h,
  cases h,
  cases h with a ha,
  have hq : P a ∨ Q a,
    left,
    assumption,
  existsi a,
  assumption,
  cases h with a ha,
  have hq : P a ∨ Q a,
    right,
    assumption,
  existsi a,
  assumption,

end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h,
  split,
  intro a,
  have hq: P a ∧ Q a := h a,
  cases hq,
  assumption,
  intro a,
  have hq: P a ∧ Q a := h a,
  cases hq,
  assumption,

end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro h,
  cases h,
  intro a,
  have hq: P a := h_left a,
  have hq2: Q a := h_right a,
  split,
  assumption,
  assumption,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro h,
  intro a,
  cases h,
  have hq: P a := h a,
  left,
  assumption,
  have hq: Q a := h a,
  right,
  assumption,
end

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

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

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

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

end predicate


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los retículos se verifica que
--     x ⊓ y = y ⊓ x
-- ----------------------------------------------------------------------

import order.lattice

variables {α : Type*} [lattice α]
variables x y z : α

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

lemma aux1 : x ⊓ y ≤ y ⊓ x :=
begin
  have h1 : x ⊓ y ≤ y,
    by exact inf_le_right,
  have h2 : x ⊓ y ≤ x,
    by exact inf_le_left,
  show x ⊓ y ≤ y ⊓ x,
    by exact le_inf h1 h2,
end

example : x ⊓ y = y ⊓ x :=
begin
  have h1 : x ⊓ y ≤ y ⊓ x,
    by exact aux1 x y,
  have h2 : y ⊓ x ≤ x ⊓ y,
    by exact aux1 y x,
  show x ⊓ y = y ⊓ x,
    by exact le_antisymm h1 h2,
end

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

lemma aux2 : x ⊓ y ≤ y ⊓ x :=
le_inf inf_le_right inf_le_left

example : x ⊓ y = y ⊓ x :=
le_antisymm (aux2 x y) (aux2 y x)

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

lemma aux3 : x ⊓ y ≤ y ⊓ x :=
begin
  apply le_inf,
  apply inf_le_right,
  apply inf_le_left,
end

-- Su desarrollo es
--
-- ⊢ x ⊓ y ≤ y ⊓ x
--    apply le_inf,
-- ⊢ x ⊓ y ≤ y
-- |   apply inf_le_right,
-- ⊢ x ⊓ y ≤ y
-- |   apply inf_le_left,
-- no goals

example : x ⊓ y = y ⊓ x :=
begin
  apply le_antisymm,
  apply aux3,
  apply aux3,
end

-- Su desarrollo es
--
-- ⊢ x ⊓ y = y ⊓ x
--    apply le_antisymm,
-- ⊢ x ⊓ y ≤ y ⊓ x
-- |   apply aux,
-- ⊢ y ⊓ x ≤ x ⊓ y
-- |   apply aux,
-- no goals

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

example : x ⊓ y = y ⊓ x :=
by apply le_antisymm; simp

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

example : x ⊓ y = y ⊓ x :=
-- by library_search
inf_comm

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

example : x ⊓ y = y ⊓ x :=
-- by hint
by finish

-- Lemas usados
-- ============

-- #check (inf_comm : x ⊓ y = y ⊓ x)
-- #check (inf_le_left : x ⊓ y ≤ x)
-- #check (inf_le_right : x ⊓ y ≤ y)
-- #check (le_antisymm : x ≤ y → y ≤ x → x = y)
-- #check (le_inf : z ≤ x → z ≤ y → z ≤ x ⊓ y)


-- begin header

import algebra.group

set_option trace.cc true
set_option trace.debug.dsimplify true
set_option trace.simplify.context true
set_option trace.simplify.rewrite true
-- set_option trace.debug.eqn_compiler true
-- set_option trace.debug true

-- end header

/-
# Hello
-/

/-
## Commutativity

The following is adapted from the default code
on <https://leanprover.github.io/live/latest/> .
-/

/- Lemma

$\mathbb{N}$ is commutative under addition.

-/
lemma ℕ_addition_commutative
(n m : ℕ) :
    n + m = m + n :=
begin
    -- $\mathbb{N}$ is a semi-group which is commutative under addition.
    apply add_comm
end

/-
    There're alternative tatics:

    * `apply add_comm`
    * `cc`
    * `simp [add_comm]`

    why `simp` failed? See:

    * <https://github.com/leanprover-community/lean/blob/master/doc/changes.md#v360c-26-feb-2020>
    * <https://github.com/leanprover-community/lean/pull/65>
-/

/- Example

$\mathbb{N}$ is commutative under multiplication.

(example is just a nameless lemma)
-/
example (n m : ℕ) :
    n * m = m * n :=
by apply mul_comm

-- The following are adapted from
-- Chapter 1, The Hitchhiker’s Guide to Logical Verification

-- 1.1.2 Terms

lemma function_currying_right_associative
(σ τ ν : Type) :
    ( σ → τ → ν ) = ( σ → (τ → ν) ) :=
by reflexivity

lemma application_left_associative
(σ τ ν : Type) (t : σ → τ → ν) (u : σ) (v : τ) :
    ( t u v ) = ( (t u) v ) :=
by reflexivity

lemma lambda_right_associative
(σ τ ν : Type) (t : ν) :
    ( λx : σ, λy : τ, t ) = ( λx : σ, (λy : τ, t) ) :=
by reflexivity

lemma lambda_grouping
(σ τ ν : Type) (t : ν) :
    ( λ(x : σ)(y : τ), t ) = ( λx : σ, λy : τ, t ) :=
by reflexivity

lemma lambda_type_grouping
(σ τ ν : Type) (t : ν) :
    ( λ(x y : σ), t ) = ( λ(x : σ)(y : σ), t ) :=
by reflexivity

constants a b : ℤ
constant f : ℤ → ℤ
constant g : ℤ → ℤ → ℤ

#check a
#check f

#check λx : ℤ, g (f (g a x)) (g x b)
#check λx, g (f (g a x)) (g x b)

constant trool : Type
constants trool.true trool.false trool.maybe : trool

#check trool.maybe

-- 1.2.1 Natural Numbers

inductive ℕat : Type
| zero : ℕat
| succ : ℕat → ℕat

#print ℕat

-- 1.2.2 Arithmetic Expressions & 1.3 Function Definitions

inductive ArithmeticExpr : Type
| num : ℤ → ArithmeticExpr
| sym : string -> ArithmeticExpr
| add : ArithmeticExpr → ArithmeticExpr → ArithmeticExpr
| sub : ArithmeticExpr → ArithmeticExpr → ArithmeticExpr
| mul : ArithmeticExpr → ArithmeticExpr → ArithmeticExpr
| div : ArithmeticExpr → ArithmeticExpr → ArithmeticExpr

def eval (env : string → ℤ) : ArithmeticExpr → ℤ
| (ArithmeticExpr.num i) := i
| (ArithmeticExpr.sym s) := env s
| (ArithmeticExpr.add e₁ e₂) := eval e₁ + eval e₂
| (ArithmeticExpr.sub e₁ e₂) := eval e₁ - eval e₂
| (ArithmeticExpr.mul e₁ e₂) := eval e₁ * eval e₂
| (ArithmeticExpr.div e₁ e₂) := eval e₁ / eval e₂

def example_env₂ (key : string) : ℤ
:= if key = "any" then 100 else string.to_nat key

def example_env : string → ℤ
| "one" := 1
| "two" := 2
| "three" := 3
| "four" := 4
| "any" := 100
| key := string.to_nat key

def example_eval : ℤ
:= (eval example_env) (ArithmeticExpr.sub
    (ArithmeticExpr.div
        (ArithmeticExpr.add
            (ArithmeticExpr.sym "any") (ArithmeticExpr.num 3)
        )
        (ArithmeticExpr.num 3)
    )
    (ArithmeticExpr.mul
        (ArithmeticExpr.num 5) (ArithmeticExpr.sym "four")
    )
)
def expected_eval : ℤ
:= (100 + 3) / 3 - 5 * 4

lemma fourteen :
    example_eval = expected_eval ∧ example_eval = 14 :=
begin
apply and.intro,
refl, -- example_eval = expected_eval
refl -- example_eval = 14
end

-- 1.2.5 Lists

inductive List (α : Type) : Type
| nil : List
| cons : α → List → List

#check List.nil
#check List.cons
#check @List.nil
#check @List.cons

#check []
#check λ(α : Type) (x : α) (xs : list α), x :: xs
#check λ(α : Type) (x_1 x_ooo x_n : α), x_1 :: x_ooo :: x_n :: []

#check []
#check λ(Animal : Type) (cat : Animal) (cats : list Animal),
    cat :: cats
#check λ(Animal : Type) (cat_1 cat_ooo cat_n : Animal),
    cat_1 :: cat_ooo :: cat_n :: []

-- 1.3 Function Definitions

def add : ℕ → ℕ → ℕ
| m nat.zero := m
| m (nat.succ n) := nat.succ (add m n)

-- #eval employs an optimized interpreter
#eval add 2 7
-- #reduce uses Lean’s inference kernel, which is less efficient
#reduce add 2 7

def mul : ℕ → ℕ → ℕ
| _ nat.zero := nat.zero
| m (nat.succ n) := add m (mul m n)

#eval mul 2 7

def power : ℕ → ℕ → ℕ
| _ nat.zero := 1
| m (nat.succ n) := mul m (power m n)

#eval power 2 3

def power₂ (m : ℕ) : ℕ → ℕ
| nat.zero := 1
| (nat.succ n) := mul m (power m n)

#eval power₂ 2 3

def iter (α : Type) (z : α) (f : α → α) : ℕ → α
| nat.zero := z
| (nat.succ n) := f (iter n)

def power₃ (m n : ℕ) : ℕ
:= iter ℕ 1 (λl, m * l) n

def list_append (α : Type) : list α → list α → list α
| list.nil xs := xs
| (list.cons x xs) ys := list.cons x (list_append xs ys)

#check list_append

#eval list_append _ [3, 1] [4, 1, 5]

def list_append₂ {α : Type} : list α → list α → list α
| list.nil xs := xs
| (list.cons x xs) ys := list.cons x (list_append₂ xs ys)

#check list_append₂
#eval list_append₂ [3, 1] [4, 1, 5]

-- The at sign (@) can be used to make the implicit arguments explicit
#check @list_append₂
#eval @list_append₂ ℤ [3, 1] [4, 1, 5]
#eval @list_append₂ _ [3, 1] [4, 1, 5]

-- We can use syntactic sugar in the definition, both in the patterns
-- on the left-hand sides of := and in the right-hand sides
def list_append₃ {α : Type} : list α → list α → list α
| [] xs := xs
| (x :: xs) ys := x :: (list_append₃ xs ys)

def reverse {α : Type} : list α → list α
| [] := []
| (x :: xs) := (reverse xs) ++ [x]

-- 1.4 Lemma Statements

lemma add_commutative (m n : ℕ) :
    add m n = add n m :=
sorry

lemma add_associative (l m n : ℕ) :
    add (add l m) n = add l (add m n) :=
sorry

lemma mul_commutative (m n : ℕ) :
    mul m n = mul n m :=
sorry

lemma mul_add_distributive (l m n : ℕ) :
    mul l (add m n) = add (mul l m) (mul l n) :=
sorry

lemma reverse_reverse {α : Type} (xs : list α) :
    reverse (reverse xs) = xs :=
sorry

axiom a_less_b :
    a < b


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

import algebra.big_operators.basic
import ring_theory.polynomial.basic

/-!
# Lagrange interpolation

## Main definitions

* `lagrange.basis s x` where `s : finset F` and `x : F`: the Lagrange basis polynomial
  that evaluates to `1` at `x` and `0` at other elements of `s`.
* `lagrange.interpolate s f` where `s : finset F` and `f : s → F`: the Lagrange interpolant
  that evaluates to `f x` at `x` for `x ∈ s`.

-/

noncomputable theory
open_locale big_operators classical

universe u

namespace lagrange

variables {F : Type u} [decidable_eq F] [field F] (s : finset F)
variables {F' : Type u} [field F'] (s' : finset F')

open polynomial

/-- Lagrange basis polynomials that evaluate to 1 at `x` and 0 at other elements of `s`. -/
def basis (x : F) : polynomial F :=
∏ y in s.erase x, C (x - y)⁻¹ * (X - C y)

@[simp] theorem basis_empty (x : F) : basis ∅ x = 1 :=
rfl

@[simp] theorem eval_basis_self (x : F) : (basis s x).eval x = 1 :=
begin
  rw [basis, ← coe_eval_ring_hom, (eval_ring_hom x).map_prod, coe_eval_ring_hom,
    finset.prod_eq_one],
  intros y hy, simp_rw [eval_mul, eval_sub, eval_C, eval_X],
  exact inv_mul_cancel (sub_ne_zero_of_ne (finset.ne_of_mem_erase hy).symm)
end

@[simp] theorem eval_basis_ne (x y : F) (h1 : y ∈ s) (h2 : y ≠ x) : (basis s x).eval y = 0 :=
begin
  rw [basis,
  ← coe_eval_ring_hom, (eval_ring_hom y).map_prod, coe_eval_ring_hom,
    finset.prod_eq_zero (finset.mem_erase.2 ⟨h2, h1⟩)],
  simp_rw [eval_mul, eval_sub, eval_C, eval_X, sub_self, mul_zero]
end

theorem eval_basis (x y : F) (h : y ∈ s) : (basis s x).eval y = if y = x then 1 else 0 :=
by { split_ifs with H, { subst H, apply eval_basis_self }, { exact eval_basis_ne s x y h H } }

@[simp] theorem nat_degree_basis (x : F) (hx : x ∈ s) : (basis s x).nat_degree = s.card - 1 :=
begin
  unfold basis, generalize hsx : s.erase x = sx,
  have : x ∉ sx := hsx ▸ finset.not_mem_erase x s,
  rw [← finset.insert_erase hx, hsx, finset.card_insert_of_not_mem this, add_tsub_cancel_right],
  clear hx hsx s, revert this, apply sx.induction_on,
  { intros hx, rw [finset.prod_empty, nat_degree_one], refl },
  { intros y s hys ih hx, rw [finset.mem_insert, not_or_distrib] at hx,
    have h1 : C (x - y)⁻¹ ≠ C 0 := λ h, hx.1 (eq_of_sub_eq_zero $ inv_eq_zero.1 $ C_inj.1 h),
    have h2 : X ^ 1 - C y ≠ 0 := by convert X_pow_sub_C_ne_zero zero_lt_one y,
    rw C_0 at h1, rw pow_one at h2,
    rw [finset.prod_insert hys, nat_degree_mul (mul_ne_zero h1 h2), ih hx.2,
        finset.card_insert_of_not_mem hys, nat_degree_mul h1 h2,
        nat_degree_C, zero_add, nat_degree, degree_X_sub_C, add_comm], refl,
    rw [ne, finset.prod_eq_zero_iff], rintro ⟨z, hzs, hz⟩,
    rw mul_eq_zero at hz, cases hz with hz hz,
    { rw [← C_0, C_inj, inv_eq_zero, sub_eq_zero] at hz, exact hx.2 (hz.symm ▸ hzs) },
    { rw ← pow_one (X : polynomial F) at hz, exact X_pow_sub_C_ne_zero zero_lt_one _ hz } }
end

variables (f : s → F)

/-- Lagrange interpolation: given a finset `s` and a function `f : s → F`,
`interpolate s f` is the unique polynomial of degree `< s.card`
that takes value `f x` on all `x` in `s`. -/
def interpolate : polynomial F :=
∑ x in s.attach, C (f x) * basis s x

@[simp] theorem interpolate_empty (f) : interpolate (∅ : finset F) f = 0 :=
rfl

@[simp] theorem eval_interpolate (x) (H : x ∈ s) : eval x (interpolate s f) = f ⟨x, H⟩ :=
begin
  rw [interpolate,
  ← coe_eval_ring_hom, (eval_ring_hom x).map_sum, coe_eval_ring_hom,
      finset.sum_eq_single (⟨x, H⟩ : { x // x ∈ s })],
  { rw [eval_mul, eval_C, subtype.coe_mk, eval_basis_self, mul_one] },
  { rintros ⟨y, hy⟩ _ hyx, rw [eval_mul, subtype.coe_mk, eval_basis_ne s y x H, mul_zero],
    { rintros rfl, exact hyx rfl } },
  { intro h, exact absurd (finset.mem_attach _ _) h }
end

theorem degree_interpolate_lt : (interpolate s f).degree < s.card :=
if H : s = ∅ then by { subst H, rw [interpolate_empty, degree_zero], exact with_bot.bot_lt_coe _ }
else (degree_sum_le _ _).trans_lt $ (finset.sup_lt_iff $ with_bot.bot_lt_coe s.card).2 $ λ b _,
calc  (C (f b) * basis s b).degree
    ≤ (C (f b)).degree + (basis s b).degree : degree_mul_le _ _
... ≤ 0 + (basis s b).degree : add_le_add_right degree_C_le _
... = (basis s b).degree : zero_add _
... ≤ (basis s b).nat_degree : degree_le_nat_degree
... = (s.card - 1 : ℕ) : by { rw nat_degree_basis s b b.2 }
... < s.card : with_bot.coe_lt_coe.2 (nat.pred_lt $ mt finset.card_eq_zero.1 H)

/-- Linear version of `interpolate`. -/
def linterpolate : (s → F) →ₗ[F] polynomial F :=
{ to_fun := interpolate s,
  map_add' := λ f g, by { simp_rw [interpolate, ← finset.sum_add_distrib, ← add_mul, ← C_add],
    refl },
  map_smul' := λ c f, by { simp_rw [interpolate, finset.smul_sum, C_mul', smul_smul], refl } }

@[simp] lemma interpolate_add (f g) : interpolate s (f + g) = interpolate s f + interpolate s g :=
(linterpolate s).map_add f g

@[simp] lemma interpolate_zero : interpolate s 0 = 0 :=
(linterpolate s).map_zero

@[simp] lemma interpolate_neg (f) : interpolate s (-f) = -interpolate s f :=
(linterpolate s).map_neg f

@[simp] lemma interpolate_sub (f g) : interpolate s (f - g) = interpolate s f - interpolate s g :=
(linterpolate s).map_sub f g

@[simp] lemma interpolate_smul (c : F) (f) : interpolate s (c • f) = c • interpolate s f :=
(linterpolate s).map_smul c f

theorem eq_zero_of_eval_eq_zero {f : polynomial F'} (hf1 : f.degree < s'.card)
  (hf2 : ∀ x ∈ s', f.eval x = 0) : f = 0 :=
by_contradiction $ λ hf3, not_le_of_lt hf1 $
calc  (s'.card : with_bot ℕ)
    ≤ f.roots.to_finset.card : with_bot.coe_le_coe.2 $ finset.card_le_of_subset $ λ x hx,
        (multiset.mem_to_finset).mpr $ (mem_roots hf3).2 $ hf2 x hx
... ≤ f.roots.card : with_bot.coe_le_coe.2 $ f.roots.to_finset_card_le
... ≤ f.degree : card_roots hf3

theorem eq_of_eval_eq {f g : polynomial F'} (hf : f.degree < s'.card) (hg : g.degree < s'.card)
  (hfg : ∀ x ∈ s', f.eval x = g.eval x) : f = g :=
eq_of_sub_eq_zero $ eq_zero_of_eval_eq_zero s'
  (lt_of_le_of_lt (degree_sub_le f g) $ max_lt hf hg)
  (λ x hx, by rw [eval_sub, hfg x hx, sub_self])

theorem eq_interpolate (f : polynomial F) (hf : f.degree < s.card) :
  interpolate s (λ x, f.eval x) = f :=
eq_of_eval_eq s (degree_interpolate_lt s _) hf $ λ x hx, eval_interpolate s _ x hx

/-- Lagrange interpolation induces isomorphism between functions from `s` and polynomials
of degree less than `s.card`. -/
def fun_equiv_degree_lt : degree_lt F s.card ≃ₗ[F] (s → F) :=
{ to_fun := λ f x, f.1.eval x,
  map_add' := λ f g, funext $ λ x, eval_add,
  map_smul' := λ c f, funext $ λ x, by
    { change eval ↑x (c • f).val = (c • λ (x : s), eval ↑x f.val) x,
      rw [pi.smul_apply, smul_eq_mul, ← @eval_C F c _ x,
      ← eval_mul, eval_C, C_mul'], refl },
  inv_fun := λ f, ⟨interpolate s f, mem_degree_lt.2 $ degree_interpolate_lt s f⟩,
  left_inv := λ f, subtype.eq $ eq_interpolate s f $ mem_degree_lt.1 f.2,
  right_inv := λ f, funext $ λ ⟨x, hx⟩, eval_interpolate s f x hx }

end lagrange


import data.real.basic

--OUTPUT 1
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (9 / 2), from by auto [arithmetic_mean_harmonic_mean],
  have h2 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b) ) ≥ (3 / (b + c + a + c + a + b) ), from by auto [h1, mul_div_div_eq_mul_div] using [mul_comm],
  have h3 : (a + b + c) > (0 : ℝ), from by auto [lt_iff_add_one_le, one_mul],
  have h4 : (b + c) * (a + c) * (a + b) > (0 : ℝ), from by auto [zero_mul, lt_mul_iff_one_lt_left, lt_add_one_left, hb, hc, ha],
  have h5 : (b + c + a + c + a + b) > (0 : ℝ), from by auto [lt_iff_add_one_le, one_mul, h3, lt_add_one_left],

  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) : by auto [h2, h4, h5, mul_pos, add_pos, add_pos, add_pos, mul_div_div_eq_mul_div] using [mul_comm]
end

--OUTPUT 2
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) : by auto [arithmetic_mean_ge_harmonic_mean, inv_add_cancel_left, inv_add_cancel_right] using [add_le_add_left]
end

--OUTPUT 3
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (9 / 2), from by auto [div_add, div_add, div_add, add_div, add_div, add_div, mul_div_assoc, mul_div_assoc, mul_div_assoc, mul_div_cancel],

  have h2 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (3 / 2) * (2 / 3), from by auto [mul_div_cancel, mul_div_cancel, mul_div_cancel],

  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) , from
  begin
    calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) = (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) : by auto [div_add, div_add, div_add, add_div, add_div, add_div] using [ring, mul_comm]
    ... ≥ (3 / 2), from by auto [h1, h2],
  end
end

--OUTPUT 4
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : 0 < (b + c), from by auto [ha, hb, hc, add_pos],
  have h2 : 0 < (a + c), from by auto [ha, hb, hc, add_pos],
  have h3 : 0 < (a + b), from by auto [ha, hb, hc, add_pos],

  have h4 : (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) ≥ (3 / (a + b + c)), from by auto [ha, hb, hc, nonzero_mul_div_nonzero_cancel_left, nonzero_mul_div_nonzero_cancel_right, nonzero_add_nonzero, real.harmonic_mean_le_arithmetic, harmonic_mean_sum_le_harmonic_mean],
  have h5 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 * (a + b + c) / (b + c + a + c + a + b)), from by auto [ha, hb, hc, div_eq_div_iff_mul_eq', nonzero_mul_div_nonzero_cancel_left, nonzero_add_nonzero, h4],
  have h6 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 * (a + b + c) / (3 * (a + b + c))), from by auto [ha, hb, hc, div_eq_div_iff_mul_eq', h5, nonzero_add_nonzero, mul_left_cancel],
  have h7 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ ((3 / 2) * (a + b + c) / (a + b + c)), from by auto [div_eq_div_iff_mul_eq', h6, ha, hb, hc, nonzero_mul_div_nonzero_cancel_left, nonzero_add_nonzero],
  have h8 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ ((3 / 2) * (a + b + c) / (3 / 2)), from by auto [div_eq_div_iff_mul_eq', h7, ha, hb, hc, nonzero_mul_div_nonzero_cancel_left, nonzero_add_nonzero],
  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2), from by auto [h8, mul_left_inj, ha, hb, hc, nonzero_mul_div_nonzero_cancel_left, nonzero_add_nonzero],
end

--OUTPUT 5
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  let t1 := a + b + c,
  let t2 := b + c + a + c + a + b,
  let t3 := b + c + a + c + a + b,

  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (a / (b + c)) + (b / (a + c)) + (c / (a + b)) + 3 : by auto [t1]
  ... ≥ (t1 / t2) * (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) + 3 : by auto [t2, t3]
  ... ≥ (1 / (b + c)) + (1 / (a + c)) + (1 / (a + b)) + 3 : by auto [t1, t2, t3]
  ... ≥ (3 / 2) : by auto [t1, t2, t3, arithmetic_mean_harmonic_mean]
end

--OUTPUT 6
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (a + b + c) / (b + c + a + b) = 3 / 2, from by linarith,
  have h2 : (a + b + c) / (a + c + a + b) = 3 / 2, from by linarith,
  have h3 : (a + b + c) / (a + b + b + c) = 3 / 2, from by linarith,
  have h4 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 9 / 2, from  by auto [mul_left_mono],
  have h5 : (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3 ≥ 3 / (b + c + a + b), from by auto [mul_left_mono],
  have h6 : (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3 ≥ 3 / (a + c + a + b), from by auto [mul_left_mono],
  have h7 : (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3 ≥ 3 / (a + b + b + c), from by auto [mul_left_mono],
  have h8 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3, from by auto [mul_left_mono],

  have h9 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) / 3, from by auto [h5, h6, h7, add_le_add, add_le_add],

  have h10 : 3 / 2 ≥ 3 / (b + c + a + b), from by linarith,
  have h11 : 3 / 2 ≥ 3 / (a + c + a + b), from by linarith,
  have h12 : 3 / 2 ≥ 3 / (a + b + b + c), from by linarith,

  have h13 : 3 / 2 ≥ (3 / (b + c + a + b) + 3 / (a + c + a + b) + 3 / (a + b + b + c)) / 3, from by auto [arithmetic_mean_harmonic_mean_ge],
  have h14 : (3 / (b + c + a + b) + 3 / (a + c + a + b) + 3 / (a + b + b + c)) / 3 ≥ 3 / (b + c + a + b), from by auto [mul_left_mono],
  have h15 : (3 / (b + c + a + b) + 3 / (a + c + a + b) + 3 / (a + b + b + c)) / 3 ≥ 3 / (a + c + a + b), from by auto [mul_left_mono],
  have h16 : (3 / (b + c + a + b) + 3 / (a + c + a + b) + 3 / (a + b + b + c)) / 3 ≥ 3 / (a + b + b + c), from by auto [mul_left_mono],

  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ 3 / 2, from by auto [mul_add, h13, h10, h11, h12, h14, h15, h16, h1, h2, h3, h4, h8, h9, add_le_add, add_le_add, add_le_add],
end

--OUTPUT 7
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : 0 < (a + b + c), from by auto [real.pos_add, ha, hb, hc],
  have h2 : 0 < (b + c), from by auto [real.pos_add, hb, hc],
  have h3 : 0 < (a + c), from by auto [real.pos_add, ha, hc],
  have h4 : 0 < (a + b), from by auto [real.pos_add, ha, hb],

  calc ((a / (b + c)) + (b / (a + c)) + (c / (a + b))) = ((a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b)) : by auto [div_add_div_same] using [ha, hb, hc, h2, h3, h4]
  ... ≥ ((9 / 2) * (a + b + c) / ((b + c) + (a + c) + (a + b))) : by auto [div_ge_div_of_le_of_pos, div_add_div_same, div_le_div_of_le_of_pos, ha, hb, hc, h1, h2, h3, h4]
  ... = ((9 / 2) * (a + b + c)) / (a + b + c + a + c + b) : by auto [add_left_cancel, ha, hb, hc, h1, h2, h3, h4]
  ... = 3 / (a + b + c + a + c + b) : by auto [h1, div_mul_cancel],
end

--OUTPUT 8
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) = ((a / (b + c)) + (b / (a + c)) + (c / (a + b))) + (3 / (a + b + c)) : by auto [ring]
  ... = ((a / (b + c)) + (b / (a + c)) + (c / (a + b))) + (((b + c) / (a + b + c)) + ((a + c) / (a + b + c)) + ((a + b) / (a + b + c))) : by auto [ring]
  ... = ((a / (b + c)) + ((b / (a + c)) + (c / (a + b)))) + (((b + c) / (a + b + c)) + ((a / (a + c)) + ((a + c) / (a + b + c)))) + ((a + b) / (a + b + c)) : by auto [add_assoc]
  ... = (a / (b + c) + (b + c) / (a + b + c)) + ((b / (a + c) + (a + c) / (a + b + c)) + (c / (a + b) + (a + b) / (a + b + c))) : by auto [add_assoc]
  ... = (a / (b + c) + (b + c) / (a + b + c)) + (((b + a) / (a + c) + (c + a) / (a + b + c)) + (c / (a + b) + (a + b) / (a + b + c))) : by auto [add_comm]
  ... = (a / (b + c) + (b + c) / (a + b + c)) + (((b + a) / (a + c) + (c + a) / (a + b + c)) + ((c + a) / (a + b) + (b + a) / (a + b + c))) : by auto [add_comm]
  ... = ((a / (b + c) + (b + c) / (a + b + c)) + ((b + a) / (a + c) + (c + a) / (a + b + c))) + ((c + a) / (a + b) + (b + a) / (a + b + c)) : by auto [add_assoc]
  ... = ((a / (b + c) + (a / (a + c))) + ((b + c) / (a + b + c) + (c + a) / (a + b + c))) + ((c + a) / (a + b) + (b + a) / (a + b + c)) : by auto [add_assoc]
  ... = ((a / (b + c) + (a / (a + c))) + (((b + c) / (a + b + c) + (c + a) / (a + b + c)) + ((c + a) / (a + b) + (b + a) / (a + b + c)))) : by auto [add_assoc]
  ... = ((a / (b + c) + (a / (a + c))) + (((b + c) / (a + b + c) + (c + a) / (a + b + c)) + ((c + a) / (a + b) + (b + a) / (a + b + c)))) : by auto [add_assoc]
  ... = ((a / (b + c) + (a / (a + c))) + (((b + c) / (a + b + c) + (c + a) / (a + b + c)) + ((a + c) / (a + b) + (b + c) / (a + b + c)))) : by auto [add_comm]
  ... = (a / (b + c) + (a / (a + c)) + ((b + c) / (a + b + c) + ((c + a) / (a + b + c) + ((a + c) / (a + b) + (b + c) / (a + b + c))))) : by auto [add_assoc]
  ... = (a / (b + c) + (a / (a + c)) + ((b + c) / (a + b + c) + ((c + a) / (a + b + c) + ((a + c) / (a + b) + (b + c) / (a + b + c))))) : by auto [add_assoc]
  ... = (a / (b + c) + (a / (a + c)) + (((b + c) / (a + b + c) + ((c + a) / (a + b + c) + ((a + c) / (a + b) + (b + c) / (a + b + c)))))) : by auto [add_assoc]
  ... = (a / (b + c) + (a / (a + c)) + ((b + c) / (a + b + c) + ((c + a) / (a + b + c) + ((a + c) / (a + b) + (b + c) / (a + b + c))))) : by auto [add_assoc]
  ... = a / (b + c) + (a / (a + c) + (b + c) / (a + b + c)) : by auto [add_comm]
  ... = a / (b + c) + (a + b + c) / (a + b + c) : by auto [mul_comm]
  ... = a / (b + c) + (1 : ℝ) : by auto [mul_one, mul_comm]
  ... = a / (b + c) + 1 : by auto [add_comm]
  ... = (a + 1) / (b + c) : by auto [add_mul]
  ... = (a + (1 : ℝ)) / (b + c) : by auto [add_comm]
  ... = (a + (1 : ℝ)) / (b + c) : by auto [add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, mul_comm]
  ... = (a + 1 : ℝ) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]
  ... = (a + 1) / (b + c) : by auto [add_comm, add_comm]

  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2), from by auto [add_comm, add_comm]
end

--OUTPUT 9
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1: (a + b + c) / 2 ≥ 0, from by auto [le_of_lt],

  have h2 : (a + b + c) / 2 ≥ ((a + b + c) / (b + c)) * (b + c) / 2, from by auto [am_hm, h1],
  have h3 : (a + b + c) / 2 ≥ ((a + b + c) / (a + c)) * (a + c) / 2, from by auto [am_hm, h1],
  have h4 : (a + b + c) / 2 ≥ ((a + b + c) / (a + b)) * (a + b) / 2, from by auto [am_hm, h1],

  have h5 : (a + b + c) / 2 ≥ ((b + c) + (a + c) + (a + b))/2, from by auto [h2, h3, h4, add_le_add],
  have h6 : (a + b + c) / 2 ≥ (a + b + c) / (3/2), from by auto [lt_div_iff, h2, h3, h4, add_pos_of_pos_of_pos, h1],
  have h7 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 3 * (a + b + c) / (3/2), from by auto [h5, div_le_div, le_refl],

  calc (a / (b + c)) + (b / (a + c)) + (c / (a + b)) = (c + (b + a)) / (a + (b + c)) : by auto [div_add_div]
  ... = (c + b + a) / (a + b + c) : by auto [add_comm]
  ... ≥ (3 / 2) : begin
    have h8 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 3, from by auto [h7],
    have h9 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 0, from by auto [h1, add_nonneg, add_nonneg, add_nonneg],
    have h10 : (3 / 2) * (a + b + c) / (b + c) + (3 / 2) * (a + b + c) / (a + c) + (3 / 2) * (a + b + c) / (a + b) ≥ 3 * (3 / 2), from by auto [mul_le_mul_of_nonneg_left, add_nonneg, add_nonneg, add_nonneg, h1, h8, h9],
    show (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (3 / 2), from begin
      have h11 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 0, from by auto [add_nonneg, add_nonneg, add_nonneg, h1],
      have h12 : (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ 0 * 2 * (3 / 2), from by auto [mul_nonneg, h11],
      show (a + b + c) / (b + c) + (a + b + c) / (a + c) + (a + b + c) / (a + b) ≥ (3 / 2), from by auto [h10, h12, div_le_div],
    end,
  end,
end

--OUTPUT 10
theorem nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
begin
  have h1 : (b + c) + (a + c) + (a + b) > 0, from by auto [add_pos, add_pos],
  have h2 : 1 / (b + c) + 1 / (a + c) + 1 / (a + b) ≥ 3 / (b + c + a + c + a + b), from by auto [harmonic_mean.ge, harmonic_mean.ge, harmonic_mean.ge, add_pos, add_pos, add_pos],

  have h3 : (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) ≥ 3 * (a + b + c), from by auto [h2, mul_le_mul, h1, le_of_lt],

  have h4 : (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) ≥ 9 * (a + b + c) / 2, from by auto [h3, mul_le_mul_left, div_le_div_right],

  have h5 : (a + b + c) * (1 / (b + c) + 1 / (a + c) + 1 / (a + b)) ≥ 9 * (a + b + c) / 2, from by auto [div_le_div_right, le_of_lt],

  have h6 : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ 9 / 2, from by auto [h4, mul_div_cancel'],

  show (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 : ℝ) / 2, from by auto [h6, div_le_div_right],
end

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by auto [set.subset_of_mem_powerset, set.subset_of_mem_powerset],
  have h2 : (A ∩ B) ⊆ A, from by auto [set.inter_subset_left],
  have h3 : (A ∩ B) ⊆ S, from by auto [set.subset.trans],
  show (A ∩ B) ∈  𝒫 S, from by auto [set.mem_powerset],
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by auto [sq]
  ... = x*(x+y) + y*(x+y) : by auto [add_mul]
  ... = x*x + x*y + y*x + y*y : by auto [mul_comm, add_mul] using [ring]
  ... = x^2 + 2*x*y + y^2 : by auto [sq, mul_comm] using [ring]
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by auto using [use (a⁻¹ * b)],
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by auto using [use b * a⁻¹],

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from by auto [h1],
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from by auto [h2],

  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from by auto [exists_unique.unique, h3, classical.some_spec, exists_unique.exists, mul_one],
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from by auto [exists_unique.unique, h4, classical.some_spec, exists_unique.exists, one_mul],

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by auto [h3, h4, exists_unique.unique, classical.some_spec, exists_unique.exists] using [use (1 : G)],
end

/--`theorem`
Nesbitt's inequality
Let $a$, $b$ and $c$ be positive real numbers.

Then:
:$\dfrac a {b + c} + \dfrac b {a + c} + \dfrac c {a + b} \ge \dfrac 3 2$

`proof`
{{begin-eqn}}
{{eqn | l = \frac a {b + c} + \frac b {a + c} + \frac c {a + b}
      | o = \ge
      | r = \dfrac 3 2
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {a + b + c} {b + c} + \frac {a + b + c} {a + c} + \frac {a + b + c} {a + b}
      | o = \ge
      | r = \frac 9 2
      | c = by adding $3$
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {a + b + c} {b + c} + \frac {a + b + c} {a + c} + \frac {a + b + c} {a + b}
      | o = \ge
      | r = \frac {9 \paren {a + b + c} } {\paren {b + c} + \paren {a + c} + \paren {a + b} }
      | c = as $\dfrac {a + b + c} {\paren {b + c} + \paren {a + c} + \paren {a + b} } = \dfrac 1 2$
}}
{{eqn | ll= \leadstoandfrom
      | l = \frac {\frac 1 {b + c} + \frac 1 {a + c} + \frac 1 {a + b} } 3
      | o = \ge
      | r = \frac 3 {\paren {b + c} + \paren {a + c} + \paren {a + b} }
      | c = dividing by $3 \paren {a + b + c}$
}}
{{end-eqn}}
These are the arithmetic mean and the harmonic mean of $\dfrac 1 {b + c}$, $\dfrac 1 {a + c}$ and $\dfrac 1 {a + b}$.

From Arithmetic Mean is Never Less than Harmonic Mean the last inequality is true.

Thus Nesbitt's Inequality holds.
{{qed}}

-/
theorem  nesbitt_inequality {a b c : ℝ} (ha : 0 < a) (hb : 0 < b) (hc : 0 < c) : (a / (b + c)) + (b / (a + c)) + (c / (a + b)) ≥ (3 / 2) :=
FEW SHOT PROMPTS TO CODEX(END)-/


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura

! This file was ported from Lean 3 source module init.data.fin.basic
! leanprover-community/mathlib commit 3626c1e18e15a96099f9d639e2e0a719273f25ef
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
prelude
import Leanbin.Init.Data.Nat.Basic

open Nat

#print Fin /-
/-- `fin n` is the subtype of `ℕ` consisting of natural numbers strictly smaller than `n`. -/
structure Fin (n : ℕ) where
  val : Nat
  property : val < n
#align fin Fin
-/

attribute [pp_using_anonymous_constructor] Fin

namespace Fin

protected def Lt {n} (a b : Fin n) : Prop :=
  a.val < b.val
#align fin.lt Fin.Lt

protected def Le {n} (a b : Fin n) : Prop :=
  a.val ≤ b.val
#align fin.le Fin.Le

instance {n} : LT (Fin n) :=
  ⟨Fin.Lt⟩

instance {n} : LE (Fin n) :=
  ⟨Fin.Le⟩

instance decidableLt {n} (a b : Fin n) : Decidable (a < b) :=
  Nat.decidableLt _ _
#align fin.decidable_lt Fin.decidableLt

instance decidableLe {n} (a b : Fin n) : Decidable (a ≤ b) :=
  Nat.decidableLe _ _
#align fin.decidable_le Fin.decidableLe

def elim0.{u} {α : Fin 0 → Sort u} : ∀ x : Fin 0, α x
  | ⟨n, h⟩ => absurd h n.not_lt_zero
#align fin.elim0 Fin.elim0ₓ

variable {n : Nat}

#print Fin.eq_of_veq /-
theorem eq_of_veq : ∀ {i j : Fin n}, i.val = j.val → i = j
  | ⟨iv, ilt₁⟩, ⟨iv, ilt₂⟩, rfl => rfl
#align fin.eq_of_veq Fin.eq_of_veq
-/

#print Fin.veq_of_eq /-
theorem veq_of_eq : ∀ {i j : Fin n}, i = j → i.val = j.val
  | ⟨iv, ilt⟩, _, rfl => rfl
#align fin.veq_of_eq Fin.veq_of_eq
-/

#print Fin.ne_of_vne /-
theorem ne_of_vne {i j : Fin n} (h : i.val ≠ j.val) : i ≠ j := fun h' => absurd (veq_of_eq h') h
#align fin.ne_of_vne Fin.ne_of_vne
-/

#print Fin.vne_of_ne /-
theorem vne_of_ne {i j : Fin n} (h : i ≠ j) : i.val ≠ j.val := fun h' => absurd (eq_of_veq h') h
#align fin.vne_of_ne Fin.vne_of_ne
-/

end Fin

open Fin

instance (n : Nat) : DecidableEq (Fin n) := fun i j =>
  decidable_of_decidable_of_iff (Nat.decidableEq i.val j.val) ⟨eq_of_veq, veq_of_eq⟩


/-
Copyright (c) 2022 Mantas Bakšys. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mantas Bakšys
-/
import data.real.basic
import data.nat.interval
import algebra.order.rearrangement
import algebra.big_operators.ring

/-!
# IMO 1975 Q1

Let `x₁, x₂, ... , xₙ` and `y₁, y₂, ... , yₙ` be two sequences of real numbers, such that
`x₁ ≥ x₂ ≥ ... ≥ xₙ` and `y₁ ≥ y₂ ≥ ... ≥ yₙ`. Prove that if `z₁, z₂, ... , zₙ` is any permutation
of `y₁, y₂, ... , yₙ`, then `∑ (xᵢ - yᵢ)^2 ≤ ∑ (xᵢ - zᵢ)^2`

# Solution

Firstly, we expand the squares withing both sums and distribute into separate finite sums. Then,
noting that `∑ yᵢ ^ 2 = ∑ zᵢ ^ 2`, it remains to prove that `∑ xᵢ * zᵢ ≤ ∑ xᵢ * yᵢ`, which is true
by the Rearrangement Inequality
-/

open_locale big_operators

/- Let `n` be a natural number, `x` and `y` be as in the problem statement and `σ` be the
permutation of natural numbers such that `z = y ∘ σ` -/
variables (n : ℕ) (σ : equiv.perm ℕ) (hσ : {x | σ x ≠ x} ⊆ finset.Icc 1 n) (x y : ℕ → ℝ)
variables (hx : antitone_on x (finset.Icc 1 n))
variables (hy : antitone_on y (finset.Icc 1 n))
include hx hy hσ

theorem IMO_1975_Q1 :
  ∑ i in finset.Icc 1 n, (x i - y i) ^ 2 ≤ ∑ i in finset.Icc 1 n, (x i - y (σ i)) ^ 2 :=
begin
  simp only [sub_sq, finset.sum_add_distrib, finset.sum_sub_distrib],
  -- a finite sum is invariant if we permute the order of summation
  have hσy : ∑ (i : ℕ) in finset.Icc 1 n, y i ^ 2 = ∑ (i : ℕ) in finset.Icc 1 n, y (σ i) ^ 2,
  { rw ← equiv.perm.sum_comp σ (finset.Icc 1 n) _ hσ },
  -- let's cancel terms appearing on both sides
  norm_num [hσy, mul_assoc, ← finset.mul_sum],
  -- what's left to prove is a version of the rearrangement inequality
  apply monovary_on.sum_mul_comp_perm_le_sum_mul _ hσ,
  -- finally we need to show that `x` and `y` 'vary' together on `[1, n]` and this is due to both of
  -- them being `decreasing`
  exact antitone_on.monovary_on hx hy
end


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

import tactic -- imports all the Lean tactics
import data.polynomial -- API for (i.e. definitions relating to and lemmas about) polynomials

/-!

# Degree

What is the degree of the zero polynomial? Is it 0, -1 or -∞? One advantage
of -∞ is that things like `deg(f*g)=deg(f)+deg(g)` is always true (at least
if the base is an integral domain) but one big disadvantage is that working
with a type like `with_bot ℕ` (which is the naturals plus a "bottom" element
`⊥` which is less than all naturals) is a pain; it's sometimes much easier to
define `deg(0)` to be the "junk" value `0` and have a degree function
taking values in the naturals.

Lean has both options: there is a function `polynomial.degree` taking
values in `with_bot ℕ` and also a function `polynomial.nat_degree`
taking values in `ℕ`. In this file we use `nat_degree` because for
what we're doing here the benefits outweigh the gains.

## M(R,n), the space of polynomials of degree ≤ n

We are about to embark upon a long proof; a proof that if `R` is a Noetherian
ring (i.e. all its ideals are finitely-generated) then so is `R[X]`.
One of the players in it will be the free `R`-module of polynomials
of degree at most `n`. We make this definition explicitly. I've
left the proofs of the axioms for you to fill in; you are going to have
to figure out the API for `polynomial.nat_degree` to solve them. Guess
the names of the lemmas you'll need; everything is there somewhere.
If you really can't guess, then browse around the API documentation
for `nat_degree` here:

https://leanprover-community.github.io/mathlib_docs/data/polynomial/degree/definitions.html#polynomial.nat_degree

-/

namespace polynomial

variables (R : Type) [comm_ring R]

/-- `M R n` is the `R`-submodule of `R[X]` consisting of polynomials of degree ≤ `n`. -/
noncomputable def M (n : ℕ) : submodule R (polynomial R) :=
{ carrier := {f : polynomial R | f.nat_degree ≤ n},
  zero_mem' := begin
    sorry
  end,
  add_mem' := begin
    sorry,
  end,
  smul_mem' := begin
    sorry
  end
}

-- think about what `f ∈ M R n` means

lemma mem_M_iff (f : polynomial R) (n : ℕ) : f ∈ M R n ↔ f.nat_degree ≤ n :=
begin
  sorry,
end

/-

## lcoeff

The function `polynomial.coeff` eats a polynomial `f` and a natural `n`, and
returns the coefficient of `X^n` in `f`. However this is just a bare function.
For fixed `n`, the function which eats `f` and returns the coefficient of
`X^n` in `f` is an `R`-linear map, and this `R`-linear map
is called `polynomial.lcoeff R n : polynomial R →ₗ[R] R`. The notation
`→ₗ[R]` means "R-linear map".

The lemma below, which will be of use for us later, says that a polynomial
of degree ≤ n+1 whose coefficient of X^{n+1} is 0, is actually a polynomial
of degree ≤ n. The proof is not too bad once you know about
`nat_degree_le_iff_coeff_eq_zero`.

-/

lemma ker_lcoeff (f : polynomial R) (n : ℕ) (hfn : f ∈ M R (n+1)) (hf : lcoeff R (n+1) f = 0) :
  f ∈ M R n :=
begin
  sorry,
end

/-

Finally, this lemma below, saying that the degree of X^n is at most n,
seems to be missing from mathlib. You would have thought that this
was a strange thing to prove because surely the degree of X^n *equals* n,
but in fact there is an edge case where this is false! Can you think of it?

Spoiler alert below.

If R is the zero ring, then R[X] is the zero ring, and X^n=X^2=X=1=0
so clearly X^n can't have degree n. It turns out that
there's a lemma `nat_degree_of_subsingleton` saying that if `R`
is a so-called "subsingleton" (mathlib's preferred say of saying
that `R` is the zero ring) then every polynomial has `nat_degree` 0.
For `nontrivial` rings (those with `0 ≠ 1`), `nat_degree_X_pow`
works. So your proof could do `cases` on `subsingleton_or_nontrivial R`,
except that because `subsingleton` and `nontrivial` are both typeclasses,
you should use the `casesI` tactic to ensure that the assumptions
are known to the type class inference system.

-/

-- this should be PR'ed to mathlib
lemma nat_degree_X_pow_le (n : ℕ) : (X^n : polynomial R).nat_degree ≤ n :=
begin
  sorry,
end

end polynomial


import Mathlib

/-!
# Minimum of a list of natural numbers

Our next example is an algorithm to find the minimum of a non-empty list of natural numbers, with a proof of correctness. The proof of correctness consists of two statements about the algorithm: (1) the minimum is in the list, and (2) the minimum is less than or equal to each element in the list.

To avoid name collisions we will use the name `smallest`
-/

/-- The smallest element in a non-empty list of natural numbers  -/
def smallest (l : List ℕ)(_: l ≠ []) : ℕ :=
  match l with
  | head :: tail =>
      if c:tail = [] then head
      else
       Nat.min head (smallest tail c)

/-!
An example

```lean
#eval smallest [7, 8, 1,2,3,4,5] (by decide)
```
-/

#eval smallest [7, 8, 1,2,3,4,5] (by decide)

/-- the result of `smallest` applied to a list is a member of that list -/
theorem smallest_in_list
  (l : List ℕ)(hyp : l ≠ []) :
    smallest l hyp ∈ l := by
  match l with
  | head :: tail =>
      by_cases c:tail = []
      · simp [smallest, c]
      · simp [smallest, c]
        by_cases c':head ≤ smallest tail (by simp [c])
        · left
          assumption
        · right
          have lem :
            Nat.min head (smallest tail (by simp [c])) = smallest tail (by simp [c]) := by
              apply Nat.min_eq_right
              apply Nat.le_of_not_le
              assumption
          rw [lem]
          simp [smallest_in_list]

#check Nat.min_eq_right -- Nat.min_eq_right {a b : ℕ} (h : b ≤ a) : min a b = b

/-- An example of using `left` and `right` tactics. -/
def egNat : ℕ := by
  right
  left

#eval egNat -- 1

/--
`smallest` is less than or equal to each element in the list
-/
theorem smallest_le (l : List ℕ) (hyp : l ≠ []) :
  ∀ m : ℕ, m ∈ l → smallest l hyp ≤ m  := match l with
  | head :: tail => by
    simp [smallest, hyp]
    apply And.intro
    · split  <;> simp
    · intro a hyp'
      have c'' : tail ≠ [] := by
        intro contra
        rw [contra] at hyp'
        contradiction
      simp [c'']
      right
      exact smallest_le tail c'' a hyp'

#check List.mem_cons


import data.real.basic
open function

/-
# Chapter 6 : Functions

## Level 4

A classical result in composition of functions.
-/

-- inverses
def two_sided_inverse {X Y : set ℝ} (f : X → Y) (g : Y → X)
:= (∀ x : X, (g ∘ f)(x) = x) ∧ (∀ y : Y, (f ∘ g)(y) = y)

/- Lemma
A function $f : X → Y$ has a two-sided inverse if and only if it is a bijection.
-/
theorem exist_two_sided_inverse (X Y : set ℝ) (f : X → Y) :
    (∃ g : Y → X, two_sided_inverse f g) ↔ bijective f :=
begin
    split,
    {
        intro Eg,
        cases Eg with g invg,
        cases invg with gf fg,
        -- show f is injective
        have hinjf : injective f,
            intros x1 x2 f1f2,
            replace f1f2 : g ( f x1 ) = g ( f x2 ),
            rw f1f2,
            have h1 : g (f x1) = x1 := gf x1,
            have h2 : g (f x2) = x2 := gf x2,
            rw [ h1, h2 ] at f1f2, exact f1f2,
        -- show f is surjective
        have hsurf : surjective f,
            intro y,
            existsi g y,
            exact fg y,
        -- put these results together
        exact and.intro hinjf hsurf,
    },
    {
        rintro bf,
        cases bf with finj fsurj,
        -- Since $f$ is surjective, $∀ y ∈ Y, ∃ x ∈ X$ such that $f(x) = y$;
        -- choose this $x$ to be the output of $g(y)$.
        choose g hg using fsurj,
        existsi g,
        split,
        intro x,
        -- by definition of $g$, $∀ y ∈ Y, f(g(y)) = y$
        -- so we have $f(g(f(x))) = f(x)$:
        have hx : f (g (f x)) = f x, rw hg (f x),
        apply finj,
        exact hx,
        exact hg,
    }
end